Skip to content

Release queue: minor #4641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/afraid-taxis-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

Add Layer.setRandom, for over-riding the default Random service
5 changes: 5 additions & 0 deletions .changeset/calm-zebras-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

`Brand.unbranded` getter has been added
5 changes: 5 additions & 0 deletions .changeset/fast-garlics-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

Add Record.findFirst
5 changes: 5 additions & 0 deletions .changeset/friendly-geese-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

Default `never` type has been added to `MutableHasMap.empty` & `MutableList.empty` ctors
18 changes: 18 additions & 0 deletions .changeset/funny-islands-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"effect": minor
---

add Stream.toAsyncIterable\* apis

```ts
import { Stream } from "effect"

// Will print:
// 1
// 2
// 3
const stream = Stream.make(1, 2, 3)
for await (const result of Stream.toAsyncIterable(stream)) {
console.log(result)
}
```
35 changes: 35 additions & 0 deletions .changeset/kind-poems-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
"effect": minor
---

Simplified the creation of pipeable classes.

```ts
class MyClass extends Pipeable.Class() {
constructor(public a: number) {
super()
}
methodA() {
return this.a
}
}
console.log(new MyClass(2).pipe((x) => x.methodA())) // 2
```

```ts
class A {
constructor(public a: number) {}
methodA() {
return this.a
}
}
class B extends Pipeable.Class(A) {
constructor(private b: string) {
super(b.length)
}
methodB() {
return [this.b, this.methodA()]
}
}
console.log(new B("pipe").pipe((x) => x.methodB())) // ['pipe', 4]
```
5 changes: 5 additions & 0 deletions .changeset/sharp-mirrors-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/sql-drizzle": minor
---

Export `layerWithConfig` method to build a layer with some Drizzle config
5 changes: 5 additions & 0 deletions .changeset/sixty-ducks-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

property `message: string` has been added to `ConfigError.And` & `Or` members
5 changes: 5 additions & 0 deletions .changeset/sweet-maps-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

allow catching multiple different tags in Effect.catchTag
5 changes: 5 additions & 0 deletions .changeset/wild-melons-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/platform": minor
---

Allow removing multiple Headers
11 changes: 11 additions & 0 deletions packages/effect/dtslint/Record.tst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,4 +589,15 @@ describe("Record", () => {
expect(Record.intersection(string$structAB, { b: 2 }, (a, _) => a))
.type.toBe<Record<"b", number>>()
})

it("findFirst", () => {
expect(Record.findFirst(string$numbersOrStrings, (a, _) => predicateNumbersOrStrings(a)))
.type.toBe<Option.Option<[string, string | number]>>()
expect(pipe(string$numbersOrStrings, Record.findFirst((a, _) => predicateNumbersOrStrings(a))))
.type.toBe<Option.Option<[string, string | number]>>()
expect(Record.findFirst(string$numbersOrStrings, Predicate.isString))
.type.toBe<Option.Option<[string, string]>>()
expect(pipe(string$numbersOrStrings, Record.findFirst(Predicate.isString)))
.type.toBe<Option.Option<[string, string]>>()
})
})
10 changes: 9 additions & 1 deletion packages/effect/src/Brand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
import * as Arr from "./Array.js"
import * as Either from "./Either.js"
import { identity } from "./Function.js"
import { identity, unsafeCoerce } from "./Function.js"
import * as Option from "./Option.js"
import type { Predicate } from "./Predicate.js"
import type * as Types from "./Types.js"
Expand Down Expand Up @@ -350,3 +350,11 @@ export const all: <Brands extends readonly [Brand.Constructor<any>, ...Array<Bra
is: (args: any): args is any => Either.isRight(either(args))
})
}

/**
* Retrieves the unbranded value from a `Brand` instance.
*
* @since 3.15.0
* @category getters
*/
export const unbranded: <A extends Brand<any>>(branded: A) => Brand.Unbranded<A> = unsafeCoerce
2 changes: 2 additions & 0 deletions packages/effect/src/ConfigError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export interface And extends ConfigError.Proto {
readonly _op: "And"
readonly left: ConfigError
readonly right: ConfigError
readonly message: string
}

/**
Expand All @@ -86,6 +87,7 @@ export interface Or extends ConfigError.Proto {
readonly _op: "Or"
readonly left: ConfigError
readonly right: ConfigError
readonly message: string
}

/**
Expand Down
14 changes: 6 additions & 8 deletions packages/effect/src/Effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3861,15 +3861,13 @@ export const catchSomeDefect: {
* @category Error handling
*/
export const catchTag: {
<K extends E extends { _tag: string } ? E["_tag"] : never, E, A1, E1, R1>(
k: K,
f: (e: NoInfer<Extract<E, { _tag: K }>>) => Effect<A1, E1, R1>
): <A, R>(self: Effect<A, E, R>) => Effect<A1 | A, E1 | Exclude<E, { _tag: K }>, R1 | R>
<A, E, R, K extends E extends { _tag: string } ? E["_tag"] : never, R1, E1, A1>(
<E, const K extends RA.NonEmptyReadonlyArray<E extends { _tag: string } ? E["_tag"] : never>, A1, E1, R1>(
...args: [...tags: K, f: (e: Extract<NoInfer<E>, { _tag: K[number] }>) => Effect<A1, E1, R1>]
): <A, R>(self: Effect<A, E, R>) => Effect<A | A1, Exclude<E, { _tag: K[number] }> | E1, R | R1>
<A, E, R, const K extends RA.NonEmptyReadonlyArray<E extends { _tag: string } ? E["_tag"] : never>, R1, E1, A1>(
self: Effect<A, E, R>,
k: K,
f: (e: Extract<E, { _tag: K }>) => Effect<A1, E1, R1>
): Effect<A | A1, E1 | Exclude<E, { _tag: K }>, R | R1>
...args: [...tags: K, f: (e: Extract<NoInfer<E>, { _tag: K[number] }>) => Effect<A1, E1, R1>]
): Effect<A | A1, Exclude<E, { _tag: K[number] }> | E1, R | R1>
} = effect.catchTag

/**
Expand Down
11 changes: 11 additions & 0 deletions packages/effect/src/Layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ import * as fiberRuntime from "./internal/fiberRuntime.js"
import * as internal from "./internal/layer.js"
import * as circularLayer from "./internal/layer/circular.js"
import * as query from "./internal/query.js"
import { randomTag } from "./internal/random.js"
import type { LogLevel } from "./LogLevel.js"
import type * as Option from "./Option.js"
import type { Pipeable } from "./Pipeable.js"
import type * as Random from "./Random.js"
import type * as Request from "./Request.js"
import type * as Runtime from "./Runtime.js"
import type * as Schedule from "./Schedule.js"
Expand Down Expand Up @@ -945,6 +947,15 @@ export const setConfigProvider: (configProvider: ConfigProvider) => Layer<never>
*/
export const parentSpan: (span: Tracer.AnySpan) => Layer<Tracer.ParentSpan> = circularLayer.parentSpan

/**
* @since 3.15.0
* @category Random
*/
export const setRandom = <A extends Random.Random>(random: A): Layer<never> =>
scopedDiscard(
fiberRuntime.fiberRefLocallyScopedWith(defaultServices.currentServices, Context.add(randomTag, random))
)

/**
* @since 2.0.0
* @category requests & batching
Expand Down
2 changes: 1 addition & 1 deletion packages/effect/src/MutableHashMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class BucketIterator<K, V> implements Iterator<[K, V]> {
* @since 2.0.0
* @category constructors
*/
export const empty = <K, V>(): MutableHashMap<K, V> => {
export const empty = <K = never, V = never>(): MutableHashMap<K, V> => {
const self = Object.create(MutableHashMapProto)
self.referential = new Map()
self.buckets = new Map()
Expand Down
2 changes: 1 addition & 1 deletion packages/effect/src/MutableList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const makeNode = <T>(value: T): LinkedListNode<T> => ({
* @since 2.0.0
* @category constructors
*/
export const empty = <A>(): MutableList<A> => {
export const empty = <A = never>(): MutableList<A> => {
const list = Object.create(MutableListProto)
list.head = undefined
list.tail = undefined
Expand Down
44 changes: 43 additions & 1 deletion packages/effect/src/Pipeable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
* @since 2.0.0
*/

import type { Ctor } from "./Types.js"

/**
* @since 2.0.0
* @category models
* @category Models
*/
export interface Pipeable {
pipe<A>(this: A): A
Expand Down Expand Up @@ -522,3 +524,43 @@ export const pipeArguments = <A>(self: A, args: IArguments): unknown => {
}
}
}

/**
* @since 3.15.0
* @category Models
*/
export interface PipeableConstructor {
new(...args: Array<any>): Pipeable
}

/**
* @since 3.15.0
* @category Prototypes
*/
export const Prototype: Pipeable = {
pipe() {
return pipeArguments(this, arguments)
}
}

const Base: PipeableConstructor = (function() {
function PipeableBase() {}
PipeableBase.prototype = Prototype
return PipeableBase as any
})()

/**
* @since 3.15.0
* @category Constructors
*/
export const Class: {
(): PipeableConstructor
<TBase extends Ctor>(klass: TBase): TBase & PipeableConstructor
} = (klass?: Ctor) =>
klass ?
class extends klass {
pipe() {
return pipeArguments(this, arguments)
}
}
: Base
44 changes: 44 additions & 0 deletions packages/effect/src/Record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1227,3 +1227,47 @@ export const getEquivalence = <K extends string, A>(
export const singleton = <K extends string | symbol, A>(key: K, value: A): Record<K, A> => ({
[key]: value
} as any)

/**
* Returns the first entry that satisfies the specified
* predicate, or `None` if no such entry exists.
*
* @example
* ```ts
* import { Record, Option } from "effect"
*
* const record = { a: 1, b: 2, c: 3 }
* const result = Record.findFirst(record, (value, key) => value > 1 && key !== "b")
* console.log(result) // Option.Some(["c", 3])
* ```
*
* @category elements
* @since 3.14.0
*/
export const findFirst: {
<K extends string | symbol, V, V2 extends V>(
refinement: (value: NoInfer<V>, key: NoInfer<K>) => value is V2
): (self: Record<K, V>) => Option.Option<[K, V2]>
<K extends string | symbol, V>(
predicate: (value: NoInfer<V>, key: NoInfer<K>) => boolean
): (self: Record<K, V>) => Option.Option<[K, V]>
<K extends string | symbol, V, V2 extends V>(
self: Record<K, V>,
refinement: (value: NoInfer<V>, key: NoInfer<K>) => value is V2
): Option.Option<[K, V2]>
<K extends string | symbol, V>(
self: Record<K, V>,
predicate: (value: NoInfer<V>, key: NoInfer<K>) => boolean
): Option.Option<[K, V]>
} = dual(
2,
<K extends string | symbol, V>(self: Record<K, V>, f: (value: V, key: K) => boolean) => {
for (const a of Object.entries<V>(self)) {
const o = f(a[1], a[0] as K)
if (o) {
return Option.some(a)
}
}
return Option.none()
}
)
28 changes: 28 additions & 0 deletions packages/effect/src/Stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5332,6 +5332,34 @@ export const toReadableStreamRuntime: {
): ReadableStream<A>
} = internal.toReadableStreamRuntime

/**
* Converts the stream to a `AsyncIterable` using the provided runtime.
*
* @since 3.15.0
* @category destructors
*/
export const toAsyncIterableRuntime: {
<A, XR>(runtime: Runtime<XR>): <E, R extends XR>(self: Stream<A, E, R>) => AsyncIterable<A>
<A, E, XR, R extends XR>(self: Stream<A, E, R>, runtime: Runtime<XR>): AsyncIterable<A>
} = internal.toAsyncIterableRuntime

/**
* Converts the stream to a `AsyncIterable` capturing the required dependencies.
*
* @since 3.15.0
* @category destructors
*/
export const toAsyncIterableEffect: <A, E, R>(self: Stream<A, E, R>) => Effect.Effect<AsyncIterable<A>, never, R> =
internal.toAsyncIterableEffect

/**
* Converts the stream to a `AsyncIterable`.
*
* @since 3.15.0
* @category destructors
*/
export const toAsyncIterable: <A, E>(self: Stream<A, E>) => AsyncIterable<A> = internal.toAsyncIterable

/**
* Applies the transducer to the stream and emits its outputs.
*
Expand Down
5 changes: 5 additions & 0 deletions packages/effect/src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,8 @@ export type NotFunction<T> = T extends Function ? never : T
* @since 3.9.0
*/
export type NoExcessProperties<T, U> = T & { readonly [K in Exclude<keyof U, keyof T>]: never }

/**
* @since 3.15.0
*/
export type Ctor<T = {}> = new(...args: Array<any>) => T
12 changes: 12 additions & 0 deletions packages/effect/src/internal/configError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export const And = (self: ConfigError.ConfigError, that: ConfigError.ConfigError
return `${this.left} and ${this.right}`
}
})
Object.defineProperty(error, "message", {
enumerable: false,
get(this: ConfigError.And) {
return this.toString()
}
})
return error
}

Expand All @@ -47,6 +53,12 @@ export const Or = (self: ConfigError.ConfigError, that: ConfigError.ConfigError)
return `${this.left} or ${this.right}`
}
})
Object.defineProperty(error, "message", {
enumerable: false,
get(this: ConfigError.Or) {
return this.toString()
}
})
return error
}

Expand Down
Loading