From ad4037e533dc750d02c7cf1f6dade4dcd850ffa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Fran=C3=A7ois?= Date: Mon, 16 Sep 2024 18:45:55 +0200 Subject: [PATCH 01/12] feat: add HashMap.HashMap.Entry utility type (#3618) --- .changeset/chilled-hotels-sniff.md | 5 +++++ packages/effect/dtslint/HashMap.ts | 10 +++++++++- packages/effect/src/HashMap.ts | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 .changeset/chilled-hotels-sniff.md diff --git a/.changeset/chilled-hotels-sniff.md b/.changeset/chilled-hotels-sniff.md new file mode 100644 index 0000000000..8aa434d4b6 --- /dev/null +++ b/.changeset/chilled-hotels-sniff.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +Adds HashMap.HashMap.Entry type helper diff --git a/packages/effect/dtslint/HashMap.ts b/packages/effect/dtslint/HashMap.ts index 00ed98ce10..ed8c53bc8d 100644 --- a/packages/effect/dtslint/HashMap.ts +++ b/packages/effect/dtslint/HashMap.ts @@ -1,6 +1,7 @@ -import { pipe } from "effect/Function" +import { hole, pipe } from "effect/Function" import * as HashMap from "effect/HashMap" import * as Predicate from "effect/Predicate" +import type * as Types from "effect/Types" declare const hmLiterals: HashMap.HashMap<"k", "v"> declare const numbers: HashMap.HashMap @@ -23,6 +24,13 @@ export type K = HashMap.HashMap.Key // $ExpectType "v" export type V = HashMap.HashMap.Value +// ------------------------------------------------------------------------------------- +// HashMap.Entry +// ------------------------------------------------------------------------------------- + +// $ExpectType ["k", "v"] +hole>>() + // ------------------------------------------------------------------------------------- // filter // ------------------------------------------------------------------------------------- diff --git a/packages/effect/src/HashMap.ts b/packages/effect/src/HashMap.ts index 47f3597112..f5a881f727 100644 --- a/packages/effect/src/HashMap.ts +++ b/packages/effect/src/HashMap.ts @@ -66,6 +66,22 @@ export declare namespace HashMap { * @category type-level */ export type Value> = [T] extends [HashMap] ? _V : never + + /** + * This type-level utility extracts the entry type `[K, V]` from a `HashMap` type. + * + * @example + * import { HashMap } from "effect" + * + * declare const hm: HashMap.HashMap + * + * // $ExpectType [string, number] + * type V = HashMap.HashMap.Entry + * + * @since 3.9.0 + * @category type-level + */ + export type Entry> = [Key, Value] } /** From 91dab28abb8e6b2963b4033e404426f3b5298c08 Mon Sep 17 00:00:00 2001 From: Maxim Khramtsov Date: Thu, 19 Sep 2024 03:56:57 +0200 Subject: [PATCH 02/12] `Resource` and `ScopedRed` is subtype of `Effect`. (#3626) Co-authored-by: maksim.khramtsov --- .changeset/wicked-bears-flow.md | 6 ++++++ packages/effect/dtslint/Unify.ts | 21 +++++++++++++++++++- packages/effect/src/Resource.ts | 24 ++++++++++++++++++++++- packages/effect/src/ScopedRef.ts | 23 +++++++++++++++++++++- packages/effect/src/internal/resource.ts | 21 +++++++++++++++----- packages/effect/src/internal/scopedRef.ts | 20 +++++++++++-------- packages/effect/test/Resource.test.ts | 8 ++++++++ packages/effect/test/ScopedRef.test.ts | 6 ++++++ 8 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 .changeset/wicked-bears-flow.md diff --git a/.changeset/wicked-bears-flow.md b/.changeset/wicked-bears-flow.md new file mode 100644 index 0000000000..1781cd7b07 --- /dev/null +++ b/.changeset/wicked-bears-flow.md @@ -0,0 +1,6 @@ +--- +"effect": minor +--- + +`Resource` is subtype of `Effect`. +`ScopedRed` is subtype of `Effect`. diff --git a/packages/effect/dtslint/Unify.ts b/packages/effect/dtslint/Unify.ts index 8a122c7e87..926da4eb36 100644 --- a/packages/effect/dtslint/Unify.ts +++ b/packages/effect/dtslint/Unify.ts @@ -9,6 +9,8 @@ import type * as Option from "effect/Option" import type * as Queue from "effect/Queue" import type * as RcRef from "effect/RcRef" import type * as Ref from "effect/Ref" +import type * as Resource from "effect/Resource" +import type * as ScopedRef from "effect/ScopedRef" import type * as Stream from "effect/Stream" import type * as SubscriptionRef from "effect/SubscriptionRef" import type * as SynchronizedRef from "effect/SynchronizedRef" @@ -109,8 +111,21 @@ export type DequeueUnify = Unify.Unify< | Queue.Dequeue<1> | Queue.Dequeue<"a"> > +// $ExpectType ScopedRef<1> | ScopedRef<"a"> +export type ScopedRefUnify = Unify.Unify< + | ScopedRef.ScopedRef<1> + | ScopedRef.ScopedRef<"a"> +> +// $ExpectType Resource<1, never> | Resource | Resource<1, 2> | Resource<"a", "b"> | Resource +export type ResourceUnify = Unify.Unify< + | Resource.Resource<1> + | Resource.Resource + | Resource.Resource<1, 2> + | Resource.Resource<"a", "b"> + | Resource.Resource +> -// $ExpectType 0 | Option | Ref<1> | SynchronizedRef<1> | SubscriptionRef<1> | Deferred<1, 2> | Deferred<"a", "b"> | Fiber<"a" | 1, "b" | 2> | RuntimeFiber<"a" | 1, "b" | 2> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | Ref<"A"> | SynchronizedRef<"A"> | SubscriptionRef<"A"> | FiberRef<12> | FiberRef<"a2"> | Either<1 | "A", 0 | "E"> | Effect<1 | "A", 0 | "E", "R" | "R1"> | RcRef<1 | "A", 0 | "E"> +// $ExpectType 0 | Option | Ref<1> | SynchronizedRef<1> | SubscriptionRef<1> | Deferred<1, 2> | Deferred<"a", "b"> | Fiber<"a" | 1, "b" | 2> | RuntimeFiber<"a" | 1, "b" | 2> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | ScopedRef<1> | ScopedRef<"a"> | Resource<1, 2> | Ref<"A"> | SynchronizedRef<"A"> | SubscriptionRef<"A"> | FiberRef<12> | FiberRef<"a2"> | Resource<"a", never> | Either<1 | "A", 0 | "E"> | Effect<1 | "A", 0 | "E", "R" | "R1"> | RcRef<1 | "A", 0 | "E"> export type AllUnify = Unify.Unify< | Either.Either<1, 0> | Either.Either<"A", "E"> @@ -138,5 +153,9 @@ export type AllUnify = Unify.Unify< | Queue.Queue<"a"> | Queue.Dequeue<1> | Queue.Dequeue<"a"> + | ScopedRef.ScopedRef<1> + | ScopedRef.ScopedRef<"a"> + | Resource.Resource<1, 2> + | Resource.Resource<"a"> | 0 > diff --git a/packages/effect/src/Resource.ts b/packages/effect/src/Resource.ts index dc9af2a864..8dc6debd34 100644 --- a/packages/effect/src/Resource.ts +++ b/packages/effect/src/Resource.ts @@ -4,10 +4,12 @@ import type * as Effect from "./Effect.js" import type * as Exit from "./Exit.js" import * as internal from "./internal/resource.js" +import type { Pipeable } from "./Pipeable.js" import type * as Schedule from "./Schedule.js" import type * as Scope from "./Scope.js" import type * as ScopedRef from "./ScopedRef.js" import type * as Types from "./Types.js" +import type * as Unify from "./Unify.js" /** * @since 2.0.0 @@ -28,11 +30,31 @@ export type ResourceTypeId = typeof ResourceTypeId * @since 2.0.0 * @category models */ -export interface Resource extends Resource.Variance { +export interface Resource extends Effect.Effect, Resource.Variance, Pipeable { /** @internal */ readonly scopedRef: ScopedRef.ScopedRef> /** @internal */ readonly acquire: Effect.Effect + + readonly [Unify.typeSymbol]?: unknown + readonly [Unify.unifySymbol]?: ResourceUnify + readonly [Unify.ignoreSymbol]?: ResourceUnifyIgnore +} + +/** + * @category models + * @since 3.9.0 + */ +export interface ResourceUnify extends Effect.EffectUnify { + Resource?: () => Extract> +} + +/** + * @category models + * @since 3.9.0 + */ +export interface ResourceUnifyIgnore extends Effect.EffectUnifyIgnore { + Effect?: true } /** diff --git a/packages/effect/src/ScopedRef.ts b/packages/effect/src/ScopedRef.ts index cc46efb205..8d44654afb 100644 --- a/packages/effect/src/ScopedRef.ts +++ b/packages/effect/src/ScopedRef.ts @@ -8,6 +8,7 @@ import type { Pipeable } from "./Pipeable.js" import type * as Scope from "./Scope.js" import type * as Synchronized from "./SynchronizedRef.js" import type * as Types from "./Types.js" +import type * as Unify from "./Unify.js" /** * @since 2.0.0 @@ -31,9 +32,29 @@ export type ScopedRefTypeId = typeof ScopedRefTypeId * @since 2.0.0 * @category models */ -export interface ScopedRef extends ScopedRef.Variance, Pipeable { +export interface ScopedRef extends Effect.Effect, ScopedRef.Variance, Pipeable { /** @internal */ readonly ref: Synchronized.SynchronizedRef + + readonly [Unify.typeSymbol]?: unknown + readonly [Unify.unifySymbol]?: ScopedRefUnify + readonly [Unify.ignoreSymbol]?: ScopedRefUnifyIgnore +} + +/** + * @category models + * @since 3.9.0 + */ +export interface ScopedRefUnify extends Effect.EffectUnify { + ScopedRef?: () => Extract> +} + +/** + * @category models + * @since 3.9.0 + */ +export interface ScopedRefUnifyIgnore extends Effect.EffectUnifyIgnore { + Effect?: true } /** diff --git a/packages/effect/src/internal/resource.ts b/packages/effect/src/internal/resource.ts index 74a21dc314..bd1bf5e706 100644 --- a/packages/effect/src/internal/resource.ts +++ b/packages/effect/src/internal/resource.ts @@ -4,6 +4,7 @@ import type * as Resource from "../Resource.js" import type * as Schedule from "../Schedule.js" import type * as Scope from "../Scope.js" import * as core from "./core.js" +import * as effectable from "./effectable.js" import * as fiberRuntime from "./fiberRuntime.js" import * as _schedule from "./schedule.js" import * as scopedRef from "./scopedRef.js" @@ -23,6 +24,15 @@ const resourceVariance = { _A: (_: any) => _ } +/** @internal */ +const proto: ThisType> = { + ...effectable.CommitPrototype, + commit() { + return get(this) + }, + [ResourceTypeId]: resourceVariance +} + /** @internal */ export const auto = ( acquire: Effect.Effect, @@ -46,11 +56,12 @@ export const manual = ( core.flatMap(core.context(), (env) => pipe( scopedRef.fromAcquire(core.exit(acquire)), - core.map((ref) => ({ - [ResourceTypeId]: resourceVariance, - scopedRef: ref, - acquire: core.provideContext(acquire, env) - })) + core.map((ref) => { + const resource = Object.create(proto) + resource.scopedRef = ref + resource.acquire = core.provideContext(acquire, env) + return resource + }) )) /** @internal */ diff --git a/packages/effect/src/internal/scopedRef.ts b/packages/effect/src/internal/scopedRef.ts index 2e588c36b7..dca8d7d481 100644 --- a/packages/effect/src/internal/scopedRef.ts +++ b/packages/effect/src/internal/scopedRef.ts @@ -2,11 +2,11 @@ import * as Context from "../Context.js" import type * as Effect from "../Effect.js" import type { LazyArg } from "../Function.js" import { dual, pipe } from "../Function.js" -import { pipeArguments } from "../Pipeable.js" import type * as Scope from "../Scope.js" import type * as ScopedRef from "../ScopedRef.js" import * as core from "./core.js" import * as circular from "./effect/circular.js" +import * as effectable from "./effectable.js" import * as fiberRuntime from "./fiberRuntime.js" import * as ref from "./ref.js" import * as synchronized from "./synchronizedRef.js" @@ -25,6 +25,15 @@ const scopedRefVariance = { _A: (_: any) => _ } +/** @internal */ +const proto: ThisType> = { + ...effectable.CommitPrototype, + commit() { + return get(this) + }, + [ScopedRefTypeId]: scopedRefVariance +} + /** @internal */ const close = (self: ScopedRef.ScopedRef): Effect.Effect => core.flatMap(ref.get(self.ref), (tuple) => tuple[0].close(core.exitVoid)) @@ -41,13 +50,8 @@ export const fromAcquire = ( core.flatMap((value) => circular.makeSynchronized([newScope, value] as const).pipe( core.flatMap((ref) => { - const scopedRef: ScopedRef.ScopedRef = { - [ScopedRefTypeId]: scopedRefVariance, - pipe() { - return pipeArguments(this, arguments) - }, - ref - } + const scopedRef = Object.create(proto) + scopedRef.ref = ref return pipe( fiberRuntime.addFinalizer(() => close(scopedRef)), core.as(scopedRef) diff --git a/packages/effect/test/Resource.test.ts b/packages/effect/test/Resource.test.ts index 5043b9530c..a2b7af50f5 100644 --- a/packages/effect/test/Resource.test.ts +++ b/packages/effect/test/Resource.test.ts @@ -51,4 +51,12 @@ describe("Resource", () => { assert.strictEqual(result1, 0) assert.strictEqual(result2, 0) })) + it.scoped("subtype of Effect", () => + Effect.gen(function*() { + const ref = yield* Ref.make(0) + const cached = yield* Cached.manual(ref) + const resul1 = yield* cached + + assert.strictEqual(resul1, 0) + })) }) diff --git a/packages/effect/test/ScopedRef.test.ts b/packages/effect/test/ScopedRef.test.ts index fcd55f6100..f4c942c922 100644 --- a/packages/effect/test/ScopedRef.test.ts +++ b/packages/effect/test/ScopedRef.test.ts @@ -79,4 +79,10 @@ describe("ScopedRef", () => { const ref = yield* _(Effect.scoped(ScopedRef.make(() => 0))) expect(ref.pipe(identity)).toBe(ref) })) + it.scoped("subtype of Effect", () => + Effect.gen(function*() { + const ref = yield* ScopedRef.make(() => 0) + const result = yield* ref + assert.strictEqual(result, 0) + })) }) From 315fb388634247785c1333a465415764970cbc0a Mon Sep 17 00:00:00 2001 From: "Ian D. Bollinger" Date: Fri, 20 Sep 2024 00:14:43 -0400 Subject: [PATCH 03/12] Add `isRegExp` type guard (#3647) --- .changeset/tricky-oranges-melt.md | 5 +++++ packages/effect/dtslint/Predicate.ts | 7 +++++++ packages/effect/src/Predicate.ts | 16 ++++++++++++++++ packages/effect/src/RegExp.ts | 17 +++++++++++++++++ packages/effect/test/Predicate.test.ts | 6 ++++++ packages/effect/test/RegExp.test.ts | 6 ++++++ 6 files changed, 57 insertions(+) create mode 100644 .changeset/tricky-oranges-melt.md diff --git a/.changeset/tricky-oranges-melt.md b/.changeset/tricky-oranges-melt.md new file mode 100644 index 0000000000..2d31047195 --- /dev/null +++ b/.changeset/tricky-oranges-melt.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +Add an `isRegExp` type guard diff --git a/packages/effect/dtslint/Predicate.ts b/packages/effect/dtslint/Predicate.ts index d7d3a4db25..eee9006e29 100644 --- a/packages/effect/dtslint/Predicate.ts +++ b/packages/effect/dtslint/Predicate.ts @@ -194,6 +194,13 @@ if (Predicate.isTupleOfAtLeast(unknowns, 3)) { unknowns } +// ------------------------------------------------------------------------------------- +// isRegExp +// ------------------------------------------------------------------------------------- + +// $ExpectType RegExp[] +unknowns.filter(Predicate.isRegExp) + // ------------------------------------------------------------------------------------- // compose // ------------------------------------------------------------------------------------- diff --git a/packages/effect/src/Predicate.ts b/packages/effect/src/Predicate.ts index 1646a1a9c3..0fbc6f3bf8 100644 --- a/packages/effect/src/Predicate.ts +++ b/packages/effect/src/Predicate.ts @@ -665,6 +665,22 @@ export const isPromiseLike = ( input: unknown ): input is PromiseLike => hasProperty(input, "then") && isFunction(input.then) +/** + * Tests if a value is a `RegExp`. + * + * @param input - The value to test. + * + * @example + * import { Predicate } from "effect" + * + * assert.deepStrictEqual(Predicate.isRegExp(/a/), true) + * assert.deepStrictEqual(Predicate.isRegExp("a"), false) + * + * @category guards + * @since 3.9.0 + */ +export const isRegExp = (input: unknown): input is RegExp => input instanceof RegExp + /** * @since 2.0.0 */ diff --git a/packages/effect/src/RegExp.ts b/packages/effect/src/RegExp.ts index 36ce61d6a2..374a2d5b49 100644 --- a/packages/effect/src/RegExp.ts +++ b/packages/effect/src/RegExp.ts @@ -3,6 +3,23 @@ * * @since 2.0.0 */ +import * as predicate from "./Predicate.js" + +/** + * Tests if a value is a `RegExp`. + * + * @param input - The value to test. + * + * @example + * import { RegExp } from "effect" + * + * assert.deepStrictEqual(RegExp.isRegExp(/a/), true) + * assert.deepStrictEqual(RegExp.isRegExp("a"), false) + * + * @category guards + * @since 3.9.0 + */ +export const isRegExp: (input: unknown) => input is RegExp = predicate.isRegExp /** * Escapes special characters in a regular expression pattern. diff --git a/packages/effect/test/Predicate.test.ts b/packages/effect/test/Predicate.test.ts index 8f0316b2b7..b90676244a 100644 --- a/packages/effect/test/Predicate.test.ts +++ b/packages/effect/test/Predicate.test.ts @@ -334,4 +334,10 @@ describe("Predicate", () => { assert.deepStrictEqual(_.isTupleOfAtLeast([1, 2, 3], 2), true) assert.deepStrictEqual(_.isTupleOfAtLeast([1, 2, 3], 4), false) }) + + it("isRegExp", () => { + assert.deepStrictEqual(_.isRegExp(/a/), true) + assert.deepStrictEqual(_.isRegExp(null), false) + assert.deepStrictEqual(_.isRegExp("a"), false) + }) }) diff --git a/packages/effect/test/RegExp.test.ts b/packages/effect/test/RegExp.test.ts index 38844c1953..b39ceab0d0 100644 --- a/packages/effect/test/RegExp.test.ts +++ b/packages/effect/test/RegExp.test.ts @@ -2,6 +2,12 @@ import * as RegExp from "effect/RegExp" import { describe, expect, it } from "vitest" describe("RegExp", () => { + it("isRegExp", () => { + expect(RegExp.isRegExp(/a/)).toEqual(true) + expect(RegExp.isRegExp(null)).toEqual(false) + expect(RegExp.isRegExp("a")).toEqual(false) + }) + describe("escape", () => { it("should escape special characters correctly", () => { const testCases: Array<[string, string]> = [ From bc8e6d8c6229409904457b3aa061a56b28d6c21c Mon Sep 17 00:00:00 2001 From: Maxim Khramtsov Date: Fri, 20 Sep 2024 21:44:37 +0200 Subject: [PATCH 04/12] `Latch` implements `Effect` with `.await` semantics (#3638) Co-authored-by: maksim.khramtsov --- .changeset/early-poems-explode.md | 5 +++++ packages/effect/dtslint/Unify.ts | 3 ++- packages/effect/src/Effect.ts | 22 ++++++++++++++++++- .../effect/src/internal/effect/circular.ts | 10 +++++++-- packages/effect/test/Effect/latch.test.ts | 10 +++++++++ 5 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 .changeset/early-poems-explode.md diff --git a/.changeset/early-poems-explode.md b/.changeset/early-poems-explode.md new file mode 100644 index 0000000000..9817262db3 --- /dev/null +++ b/.changeset/early-poems-explode.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +`Latch` implements `Effect` with `.await` semantic diff --git a/packages/effect/dtslint/Unify.ts b/packages/effect/dtslint/Unify.ts index 926da4eb36..734093ffe3 100644 --- a/packages/effect/dtslint/Unify.ts +++ b/packages/effect/dtslint/Unify.ts @@ -125,7 +125,7 @@ export type ResourceUnify = Unify.Unify< | Resource.Resource > -// $ExpectType 0 | Option | Ref<1> | SynchronizedRef<1> | SubscriptionRef<1> | Deferred<1, 2> | Deferred<"a", "b"> | Fiber<"a" | 1, "b" | 2> | RuntimeFiber<"a" | 1, "b" | 2> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | ScopedRef<1> | ScopedRef<"a"> | Resource<1, 2> | Ref<"A"> | SynchronizedRef<"A"> | SubscriptionRef<"A"> | FiberRef<12> | FiberRef<"a2"> | Resource<"a", never> | Either<1 | "A", 0 | "E"> | Effect<1 | "A", 0 | "E", "R" | "R1"> | RcRef<1 | "A", 0 | "E"> +// $ExpectType 0 | Option | Ref<1> | SynchronizedRef<1> | SubscriptionRef<1> | Deferred<1, 2> | Deferred<"a", "b"> | Fiber<"a" | 1, "b" | 2> | RuntimeFiber<"a" | 1, "b" | 2> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | ScopedRef<1> | ScopedRef<"a"> | Resource<1, 2> | Ref<"A"> | SynchronizedRef<"A"> | SubscriptionRef<"A"> | FiberRef<12> | FiberRef<"a2"> | Resource<"a", never> | Latch | Either<1 | "A", 0 | "E"> | Effect<1 | "A", 0 | "E", "R" | "R1"> | RcRef<1 | "A", 0 | "E"> export type AllUnify = Unify.Unify< | Either.Either<1, 0> | Either.Either<"A", "E"> @@ -157,5 +157,6 @@ export type AllUnify = Unify.Unify< | ScopedRef.ScopedRef<"a"> | Resource.Resource<1, 2> | Resource.Resource<"a"> + | Effect.Latch | 0 > diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts index 090039deb2..ca890bbb2d 100644 --- a/packages/effect/src/Effect.ts +++ b/packages/effect/src/Effect.ts @@ -5408,7 +5408,7 @@ export const makeSemaphore: (permits: number) => Effect = circular.ma * @category latch * @since 3.8.0 */ -export interface Latch { +export interface Latch extends Effect { /** open the latch, releasing all fibers waiting on it */ readonly open: Effect /** release all fibers waiting on the latch, without opening it */ @@ -5419,6 +5419,26 @@ export interface Latch { readonly close: Effect /** only run the given effect when the latch is open */ readonly whenOpen: (self: Effect) => Effect + + readonly [Unify.typeSymbol]?: unknown + readonly [Unify.unifySymbol]?: LatchUnify + readonly [Unify.ignoreSymbol]?: LatchUnifyIgnore +} + +/** + * @category models + * @since 3.8.0 + */ +export interface LatchUnify extends EffectUnify { + Latch?: () => Latch +} + +/** + * @category models + * @since 3.8.0 + */ +export interface LatchUnifyIgnore extends EffectUnifyIgnore { + Effect?: true } /** diff --git a/packages/effect/src/internal/effect/circular.ts b/packages/effect/src/internal/effect/circular.ts index 53a75fea5c..4b700fd963 100644 --- a/packages/effect/src/internal/effect/circular.ts +++ b/packages/effect/src/internal/effect/circular.ts @@ -112,10 +112,16 @@ export const unsafeMakeSemaphore = (permits: number): Semaphore => new Semaphore /** @internal */ export const makeSemaphore = (permits: number) => core.sync(() => unsafeMakeSemaphore(permits)) -class Latch implements Effect.Latch { +class Latch extends Effectable.Class implements Effect.Latch { waiters: Array<(_: Effect.Effect) => void> = [] scheduled = false - constructor(private isOpen: boolean) {} + constructor(private isOpen: boolean) { + super() + } + + commit() { + return this.await + } private unsafeSchedule(fiber: Fiber.RuntimeFiber) { if (this.scheduled || this.waiters.length === 0) { diff --git a/packages/effect/test/Effect/latch.test.ts b/packages/effect/test/Effect/latch.test.ts index e85d56184e..019cd8c8d9 100644 --- a/packages/effect/test/Effect/latch.test.ts +++ b/packages/effect/test/Effect/latch.test.ts @@ -30,4 +30,14 @@ describe("Latch", () => { yield* latch.release assert.deepStrictEqual(yield* fiber.await, Exit.void) })) + + it.effect("subtype of Effect", () => + Effect.gen(function*() { + const latch = yield* Effect.makeLatch() + const fiber = yield* Effect.fork(latch) + + yield* latch.open + + assert.deepStrictEqual(yield* fiber.await, Exit.void) + })) }) From 1419a6812bd2535e73388c48b92ebbe27cbe3136 Mon Sep 17 00:00:00 2001 From: Leo Conforti Date: Tue, 24 Sep 2024 18:28:24 -0500 Subject: [PATCH 05/12] `Effect.provide(managedRuntime)` and `ManagedRuntime` is subtype of `Effect, E, never>` (#3677) --- .changeset/purple-beans-jog.md | 5 ++ .changeset/smart-fishes-sit.md | 5 ++ packages/effect/dtslint/Unify.ts | 11 +++- packages/effect/src/Effect.ts | 8 +++ packages/effect/src/ManagedRuntime.ts | 47 ++++++++++++++- packages/effect/src/internal/layer.ts | 36 ++++++++--- .../effect/src/internal/managedRuntime.ts | 60 ++++++++++++------- packages/effect/test/ManagedRuntime.test.ts | 14 +++++ 8 files changed, 153 insertions(+), 33 deletions(-) create mode 100644 .changeset/purple-beans-jog.md create mode 100644 .changeset/smart-fishes-sit.md diff --git a/.changeset/purple-beans-jog.md b/.changeset/purple-beans-jog.md new file mode 100644 index 0000000000..b04f03b001 --- /dev/null +++ b/.changeset/purple-beans-jog.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +support ManagedRuntime in Effect.provide diff --git a/.changeset/smart-fishes-sit.md b/.changeset/smart-fishes-sit.md new file mode 100644 index 0000000000..4b2f2750f3 --- /dev/null +++ b/.changeset/smart-fishes-sit.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +`ManagedRuntime` is subtype of `Effect, E, never>` diff --git a/packages/effect/dtslint/Unify.ts b/packages/effect/dtslint/Unify.ts index 734093ffe3..d1b2668264 100644 --- a/packages/effect/dtslint/Unify.ts +++ b/packages/effect/dtslint/Unify.ts @@ -4,6 +4,7 @@ import * as Either from "effect/Either" import type * as Exit from "effect/Exit" import type * as Fiber from "effect/Fiber" import type * as FiberRef from "effect/FiberRef" +import type * as ManagedRuntime from "effect/ManagedRuntime" import type * as Micro from "effect/Micro" import type * as Option from "effect/Option" import type * as Queue from "effect/Queue" @@ -100,7 +101,11 @@ export type RuntimeFiberUnify = Unify.Unify< | Fiber.RuntimeFiber<1, 2> | Fiber.RuntimeFiber<"a", "b"> > - +// $ExpectType ManagedRuntime<1, 2> | ManagedRuntime<"a", "b"> +export type ManagedRuntimeUnify = Unify.Unify< + | ManagedRuntime.ManagedRuntime<1, 2> + | ManagedRuntime.ManagedRuntime<"a", "b"> +> // $ExpectType Queue<1> | Queue<"a"> export type QueueUnify = Unify.Unify< | Queue.Queue<1> @@ -125,7 +130,7 @@ export type ResourceUnify = Unify.Unify< | Resource.Resource > -// $ExpectType 0 | Option | Ref<1> | SynchronizedRef<1> | SubscriptionRef<1> | Deferred<1, 2> | Deferred<"a", "b"> | Fiber<"a" | 1, "b" | 2> | RuntimeFiber<"a" | 1, "b" | 2> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | ScopedRef<1> | ScopedRef<"a"> | Resource<1, 2> | Ref<"A"> | SynchronizedRef<"A"> | SubscriptionRef<"A"> | FiberRef<12> | FiberRef<"a2"> | Resource<"a", never> | Latch | Either<1 | "A", 0 | "E"> | Effect<1 | "A", 0 | "E", "R" | "R1"> | RcRef<1 | "A", 0 | "E"> +// $ExpectType 0 | Option | Ref<1> | SynchronizedRef<1> | SubscriptionRef<1> | Deferred<1, 2> | Deferred<"a", "b"> | Fiber<"a" | 1, "b" | 2> | RuntimeFiber<"a" | 1, "b" | 2> | ManagedRuntime<1, 2> | ManagedRuntime<"a", "b"> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | ScopedRef<1> | ScopedRef<"a"> | Resource<1, 2> | Ref<"A"> | SynchronizedRef<"A"> | SubscriptionRef<"A"> | FiberRef<12> | FiberRef<"a2"> | Resource<"a", never> | Latch | Either<1 | "A", 0 | "E"> | Effect<1 | "A", 0 | "E", "R" | "R1"> | RcRef<1 | "A", 0 | "E"> export type AllUnify = Unify.Unify< | Either.Either<1, 0> | Either.Either<"A", "E"> @@ -158,5 +163,7 @@ export type AllUnify = Unify.Unify< | Resource.Resource<1, 2> | Resource.Resource<"a"> | Effect.Latch + | ManagedRuntime.ManagedRuntime<1, 2> + | ManagedRuntime.ManagedRuntime<"a", "b"> | 0 > diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts index ca890bbb2d..2a2f95508b 100644 --- a/packages/effect/src/Effect.ts +++ b/packages/effect/src/Effect.ts @@ -38,6 +38,7 @@ import * as _runtime from "./internal/runtime.js" import * as _schedule from "./internal/schedule.js" import type * as Layer from "./Layer.js" import type { LogLevel } from "./LogLevel.js" +import type * as ManagedRuntime from "./ManagedRuntime.js" import type * as Metric from "./Metric.js" import type * as MetricLabel from "./MetricLabel.js" import type * as Option from "./Option.js" @@ -3330,12 +3331,19 @@ export const provide: { ): (self: Effect) => Effect> (context: Context.Context): (self: Effect) => Effect> (runtime: Runtime.Runtime): (self: Effect) => Effect> + ( + managedRuntime: ManagedRuntime.ManagedRuntime + ): (self: Effect) => Effect> ( self: Effect, layer: Layer.Layer ): Effect> (self: Effect, context: Context.Context): Effect> (self: Effect, runtime: Runtime.Runtime): Effect> + ( + self: Effect, + runtime: ManagedRuntime.ManagedRuntime + ): Effect> } = layer.effect_provide /** diff --git a/packages/effect/src/ManagedRuntime.ts b/packages/effect/src/ManagedRuntime.ts index d66b50dbd9..968fb4a393 100644 --- a/packages/effect/src/ManagedRuntime.ts +++ b/packages/effect/src/ManagedRuntime.ts @@ -6,8 +6,30 @@ import type * as Exit from "./Exit.js" import type * as Fiber from "./Fiber.js" import * as internal from "./internal/managedRuntime.js" import type * as Layer from "./Layer.js" -import type { Pipeable } from "./Pipeable.js" import type * as Runtime from "./Runtime.js" +import type * as Unify from "./Unify.js" + +/** + * @since 3.9.0 + * @category symbol + */ +export const TypeId: unique symbol = internal.TypeId as TypeId + +/** + * @since 3.9.0 + * @category symbol + */ +export type TypeId = typeof TypeId + +/** + * Checks if the provided argument is a `ManagedRuntime`. + * + * @param input - The value to be checked if it is a `ManagedRuntime`. + + * @since 3.9.0 + * @category guards + */ +export const isManagedRuntime: (input: unknown) => input is ManagedRuntime = internal.isManagedRuntime /** * @since 3.4.0 @@ -29,7 +51,8 @@ export declare namespace ManagedRuntime { * @since 2.0.0 * @category models */ -export interface ManagedRuntime extends Pipeable { +export interface ManagedRuntime extends Effect.Effect, ER> { + readonly [TypeId]: TypeId readonly memoMap: Layer.MemoMap readonly runtimeEffect: Effect.Effect, ER> readonly runtime: () => Promise> @@ -103,6 +126,26 @@ export interface ManagedRuntime extends Pipeable { * Dispose of the resources associated with the runtime. */ readonly disposeEffect: Effect.Effect + + readonly [Unify.typeSymbol]?: unknown + readonly [Unify.unifySymbol]?: ManagedRuntimeUnify + readonly [Unify.ignoreSymbol]?: ManagedRuntimeUnifyIgnore +} + +/** + * @category models + * @since 3.9.0 + */ +export interface ManagedRuntimeUnify extends Effect.EffectUnify { + ManagedRuntime?: () => Extract> +} + +/** + * @category models + * @since 3.9.0 + */ +export interface ManagedRuntimeUnifyIgnore extends Effect.EffectUnifyIgnore { + Effect?: true } /** diff --git a/packages/effect/src/internal/layer.ts b/packages/effect/src/internal/layer.ts index ca60bd5414..985e10a149 100644 --- a/packages/effect/src/internal/layer.ts +++ b/packages/effect/src/internal/layer.ts @@ -10,6 +10,7 @@ import type { LazyArg } from "../Function.js" import { dual, pipe } from "../Function.js" import * as HashMap from "../HashMap.js" import type * as Layer from "../Layer.js" +import type * as ManagedRuntime from "../ManagedRuntime.js" import { pipeArguments } from "../Pipeable.js" import { hasProperty } from "../Predicate.js" import type * as Runtime from "../Runtime.js" @@ -1288,6 +1289,8 @@ const provideSomeRuntime = dual< ) }) +const ManagedRuntimeTypeId: ManagedRuntime.TypeId = Symbol.for("effect/ManagedRuntime") as ManagedRuntime.TypeId + /** @internal */ export const effect_provide = dual< { @@ -1300,6 +1303,9 @@ export const effect_provide = dual< ( runtime: Runtime.Runtime ): (self: Effect.Effect) => Effect.Effect> + ( + managedRuntime: ManagedRuntime.ManagedRuntime + ): (self: Effect.Effect) => Effect.Effect> }, { ( @@ -1314,16 +1320,32 @@ export const effect_provide = dual< self: Effect.Effect, runtime: Runtime.Runtime ): Effect.Effect> + ( + self: Effect.Effect, + managedRuntime: ManagedRuntime.ManagedRuntime + ): Effect.Effect> } >( 2, ( self: Effect.Effect, - source: Layer.Layer | Context.Context | Runtime.Runtime - ): Effect.Effect> => - isLayer(source) - ? provideSomeLayer(self, source as Layer.Layer) - : Context.isContext(source) - ? core.provideSomeContext(self, source) - : provideSomeRuntime(self, source as Runtime.Runtime) + source: + | Layer.Layer + | Context.Context + | Runtime.Runtime + | ManagedRuntime.ManagedRuntime + ): Effect.Effect> => { + if (isLayer(source)) { + return provideSomeLayer(self, source as Layer.Layer) + } else if (Context.isContext(source)) { + return core.provideSomeContext(self, source) + } else if (ManagedRuntimeTypeId in source) { + return core.flatMap( + (source as ManagedRuntime.ManagedRuntime).runtimeEffect, + (rt) => provideSomeRuntime(self, rt) + ) + } else { + return provideSomeRuntime(self, source as Runtime.Runtime) + } + } ) diff --git a/packages/effect/src/internal/managedRuntime.ts b/packages/effect/src/internal/managedRuntime.ts index a647fabc57..c9a38711d5 100644 --- a/packages/effect/src/internal/managedRuntime.ts +++ b/packages/effect/src/internal/managedRuntime.ts @@ -1,9 +1,11 @@ import type * as Effect from "../Effect.js" +import * as Effectable from "../Effectable.js" import type { Exit } from "../Exit.js" import type * as Fiber from "../Fiber.js" import type * as Layer from "../Layer.js" -import type { ManagedRuntime } from "../ManagedRuntime.js" +import type * as M from "../ManagedRuntime.js" import { pipeArguments } from "../Pipeable.js" +import { hasProperty } from "../Predicate.js" import type * as Runtime from "../Runtime.js" import * as Scope from "../Scope.js" import * as effect from "./core-effect.js" @@ -12,11 +14,17 @@ import * as fiberRuntime from "./fiberRuntime.js" import * as internalLayer from "./layer.js" import * as internalRuntime from "./runtime.js" -interface ManagedRuntimeImpl extends ManagedRuntime { +interface ManagedRuntimeImpl extends M.ManagedRuntime { readonly scope: Scope.CloseableScope cachedRuntime: Runtime.Runtime | undefined } +/** @internal */ +export const TypeId: M.TypeId = Symbol.for("effect/ManagedRuntime") as M.TypeId + +/** @internal */ +export const isManagedRuntime = (u: unknown): u is M.ManagedRuntime => hasProperty(u, TypeId) + function provide( managed: ManagedRuntimeImpl, effect: Effect.Effect @@ -32,34 +40,42 @@ function provide( ) } +const ManagedRuntimeProto = { + ...Effectable.CommitPrototype, + [TypeId]: TypeId, + pipe() { + return pipeArguments(this, arguments) + }, + commit(this: ManagedRuntimeImpl) { + return this.runtimeEffect + } +} + /** @internal */ export const make = ( layer: Layer.Layer, memoMap?: Layer.MemoMap -): ManagedRuntime => { +): M.ManagedRuntime => { memoMap = memoMap ?? internalLayer.unsafeMakeMemoMap() const scope = internalRuntime.unsafeRunSyncEffect(fiberRuntime.scopeMake()) - const self: ManagedRuntimeImpl = { + const runtimeEffect = internalRuntime.unsafeRunSyncEffect( + effect.memoize( + core.tap( + Scope.extend( + internalLayer.toRuntimeWithMemoMap(layer, memoMap), + scope + ), + (rt) => { + self.cachedRuntime = rt + } + ) + ) + ) + const self: ManagedRuntimeImpl = Object.assign(Object.create(ManagedRuntimeProto), { memoMap, scope, - runtimeEffect: internalRuntime - .unsafeRunSyncEffect( - effect.memoize( - core.tap( - Scope.extend( - internalLayer.toRuntimeWithMemoMap(layer, memoMap), - scope - ), - (rt) => { - self.cachedRuntime = rt - } - ) - ) - ), + runtimeEffect, cachedRuntime: undefined, - pipe() { - return pipeArguments(this, arguments) - }, runtime() { return self.cachedRuntime === undefined ? internalRuntime.unsafeRunPromiseEffect(self.runtimeEffect) : @@ -110,6 +126,6 @@ export const make = ( internalRuntime.unsafeRunPromiseEffect(provide(self, effect), options) : internalRuntime.unsafeRunPromise(self.cachedRuntime)(effect, options) } - } + }) return self } diff --git a/packages/effect/test/ManagedRuntime.test.ts b/packages/effect/test/ManagedRuntime.test.ts index 4877112a1b..defe8e4445 100644 --- a/packages/effect/test/ManagedRuntime.test.ts +++ b/packages/effect/test/ManagedRuntime.test.ts @@ -3,6 +3,7 @@ import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as FiberRef from "effect/FiberRef" import * as Layer from "effect/Layer" +import * as it from "effect/test/utils/extend" import { assert, describe, test } from "vitest" describe.concurrent("ManagedRuntime", () => { @@ -48,4 +49,17 @@ describe.concurrent("ManagedRuntime", () => { await runtimeB.dispose() assert.strictEqual(count, 1) }) + + it.effect( + "is subtype of effect", + () => + Effect.gen(function*() { + const tag = Context.GenericTag("string") + const layer = Layer.succeed(tag, "test") + const managedRuntime = ManagedRuntime.make(layer) + const runtime = yield* managedRuntime + const result = Context.get(runtime.context, tag) + assert.strictEqual(result, "test") + }) + ) }) From 267a8253c72461c4179c7243e73741dd4b07f409 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 25 Sep 2024 13:29:56 +1200 Subject: [PATCH 06/12] fix Unify for RcRef & Deferred (#3678) --- .changeset/fast-experts-melt.md | 5 +++++ packages/effect/dtslint/Unify.ts | 32 ++++++++++++++++---------------- packages/effect/src/Deferred.ts | 2 +- packages/effect/src/Queue.ts | 4 ++-- packages/effect/src/RcRef.ts | 3 +-- packages/effect/src/Resource.ts | 3 +-- packages/effect/src/Unify.ts | 23 +++++++++-------------- 7 files changed, 35 insertions(+), 37 deletions(-) create mode 100644 .changeset/fast-experts-melt.md diff --git a/.changeset/fast-experts-melt.md b/.changeset/fast-experts-melt.md new file mode 100644 index 0000000000..fc77ccec73 --- /dev/null +++ b/.changeset/fast-experts-melt.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix Unify for RcRef & Deferred diff --git a/packages/effect/dtslint/Unify.ts b/packages/effect/dtslint/Unify.ts index d1b2668264..92069fae68 100644 --- a/packages/effect/dtslint/Unify.ts +++ b/packages/effect/dtslint/Unify.ts @@ -76,7 +76,7 @@ export type SubscriptionRefUnify = Unify.Unify< | SubscriptionRef.SubscriptionRef<1> | SubscriptionRef.SubscriptionRef<"a"> > -// $ExpectType RcRef<"a" | 1, "b" | 2> +// $ExpectType RcRef<1, 2> | RcRef<"a", "b"> export type RcRefUnify = Unify.Unify< | RcRef.RcRef<1, 2> | RcRef.RcRef<"a", "b"> @@ -130,29 +130,29 @@ export type ResourceUnify = Unify.Unify< | Resource.Resource > -// $ExpectType 0 | Option | Ref<1> | SynchronizedRef<1> | SubscriptionRef<1> | Deferred<1, 2> | Deferred<"a", "b"> | Fiber<"a" | 1, "b" | 2> | RuntimeFiber<"a" | 1, "b" | 2> | ManagedRuntime<1, 2> | ManagedRuntime<"a", "b"> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | ScopedRef<1> | ScopedRef<"a"> | Resource<1, 2> | Ref<"A"> | SynchronizedRef<"A"> | SubscriptionRef<"A"> | FiberRef<12> | FiberRef<"a2"> | Resource<"a", never> | Latch | Either<1 | "A", 0 | "E"> | Effect<1 | "A", 0 | "E", "R" | "R1"> | RcRef<1 | "A", 0 | "E"> +// $ExpectType 0 | Option | Ref<1> | Ref<"a"> | SynchronizedRef<1> | SynchronizedRef<"a"> | SubscriptionRef<1> | SubscriptionRef<"a"> | RcRef<"a", "b"> | Deferred<"a", "b"> | FiberRef<1> | FiberRef<"a"> | ManagedRuntime<"a", "b"> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | ScopedRef<1> | ScopedRef<"a"> | Resource<"a", "b"> | RcRef<1, 0> | Deferred<1, 0> | Resource<1, 0> | Latch | ManagedRuntime<1, 0> | Fiber<"a" | 1, 0 | "b"> | RuntimeFiber<"a" | 1, 0 | "b"> | Either<"a" | 1, 0 | "b"> | Effect<"a" | 1, 0 | "b", "R" | "R1"> export type AllUnify = Unify.Unify< | Either.Either<1, 0> - | Either.Either<"A", "E"> + | Either.Either<"a", "b"> | Option.Option | Option.Option - | Effect.Effect<"A", "E", "R"> + | Effect.Effect<"a", "b", "R"> | Effect.Effect<1, 0, "R1"> | Ref.Ref<1> - | Ref.Ref<"A"> + | Ref.Ref<"a"> | SynchronizedRef.SynchronizedRef<1> - | SynchronizedRef.SynchronizedRef<"A"> + | SynchronizedRef.SynchronizedRef<"a"> | SubscriptionRef.SubscriptionRef<1> - | SubscriptionRef.SubscriptionRef<"A"> + | SubscriptionRef.SubscriptionRef<"a"> | RcRef.RcRef<1, 0> - | RcRef.RcRef<"A", "E"> - | Deferred.Deferred<1, 2> + | RcRef.RcRef<"a", "b"> + | Deferred.Deferred<1, 0> | Deferred.Deferred<"a", "b"> - | FiberRef.FiberRef<12> - | FiberRef.FiberRef<"a2"> - | Fiber.Fiber<1, 2> + | FiberRef.FiberRef<1> + | FiberRef.FiberRef<"a"> + | Fiber.Fiber<1, 0> | Fiber.Fiber<"a", "b"> - | Fiber.RuntimeFiber<1, 2> + | Fiber.RuntimeFiber<1, 0> | Fiber.RuntimeFiber<"a", "b"> | Queue.Queue<1> | Queue.Queue<"a"> @@ -160,10 +160,10 @@ export type AllUnify = Unify.Unify< | Queue.Dequeue<"a"> | ScopedRef.ScopedRef<1> | ScopedRef.ScopedRef<"a"> - | Resource.Resource<1, 2> - | Resource.Resource<"a"> + | Resource.Resource<1, 0> + | Resource.Resource<"a", "b"> | Effect.Latch - | ManagedRuntime.ManagedRuntime<1, 2> + | ManagedRuntime.ManagedRuntime<1, 0> | ManagedRuntime.ManagedRuntime<"a", "b"> | 0 > diff --git a/packages/effect/src/Deferred.ts b/packages/effect/src/Deferred.ts index 10a8cc13d9..e4feb97daa 100644 --- a/packages/effect/src/Deferred.ts +++ b/packages/effect/src/Deferred.ts @@ -52,7 +52,7 @@ export interface Deferred extends Effect.Effect extends Effect.EffectUnify { - Deferred?: () => Extract> + Deferred?: () => Extract> } /** diff --git a/packages/effect/src/Queue.ts b/packages/effect/src/Queue.ts index 2951729717..e9478966ff 100644 --- a/packages/effect/src/Queue.ts +++ b/packages/effect/src/Queue.ts @@ -64,7 +64,7 @@ export type BackingQueueTypeId = typeof BackingQueueTypeId * @since 2.0.0 * @category models */ -export interface Queue extends Enqueue, Dequeue, Pipeable { +export interface Queue extends Enqueue, Dequeue { /** @internal */ readonly queue: BackingQueue /** @internal */ @@ -134,7 +134,7 @@ export interface Enqueue extends Queue.EnqueueVariance, BaseQueue, Pipe * @since 2.0.0 * @category models */ -export interface Dequeue extends Effect.Effect, Queue.DequeueVariance, BaseQueue, Pipeable { +export interface Dequeue extends Effect.Effect, Queue.DequeueVariance, BaseQueue { /** * Takes the oldest value in the queue. If the queue is empty, this will return * a computation that resumes when an item has been added to the queue. diff --git a/packages/effect/src/RcRef.ts b/packages/effect/src/RcRef.ts index e014272d9f..7586c2a6d2 100644 --- a/packages/effect/src/RcRef.ts +++ b/packages/effect/src/RcRef.ts @@ -39,8 +39,7 @@ export interface RcRef * @since 3.8.0 */ export interface RcRefUnify extends Effect.EffectUnify { - RcRef?: () => A[Unify.typeSymbol] extends RcRef | infer _ ? RcRef - : never + RcRef?: () => Extract> } /** diff --git a/packages/effect/src/Resource.ts b/packages/effect/src/Resource.ts index 8dc6debd34..1f9713e770 100644 --- a/packages/effect/src/Resource.ts +++ b/packages/effect/src/Resource.ts @@ -4,7 +4,6 @@ import type * as Effect from "./Effect.js" import type * as Exit from "./Exit.js" import * as internal from "./internal/resource.js" -import type { Pipeable } from "./Pipeable.js" import type * as Schedule from "./Schedule.js" import type * as Scope from "./Scope.js" import type * as ScopedRef from "./ScopedRef.js" @@ -30,7 +29,7 @@ export type ResourceTypeId = typeof ResourceTypeId * @since 2.0.0 * @category models */ -export interface Resource extends Effect.Effect, Resource.Variance, Pipeable { +export interface Resource extends Effect.Effect, Resource.Variance { /** @internal */ readonly scopedRef: ScopedRef.ScopedRef> /** @internal */ diff --git a/packages/effect/src/Unify.ts b/packages/effect/src/Unify.ts index dc6cdd26c5..a024a5b19c 100644 --- a/packages/effect/src/Unify.ts +++ b/packages/effect/src/Unify.ts @@ -34,26 +34,21 @@ export declare const ignoreSymbol: unique symbol */ export type ignoreSymbol = typeof ignoreSymbol -type MaybeReturn = F extends () => any ? ReturnType : F +type MaybeReturn = F extends () => infer R ? R : NonNullable -type Values = X extends any - ? { [k in keyof X[0]]-?: k extends X[1] ? never : MaybeReturn }[keyof X[0]] +type Values = X extends [infer A, infer Ignore] + ? Exclude extends infer k ? k extends keyof A ? MaybeReturn : never : never : never -type Ignore = X extends { - [ignoreSymbol]?: any -} ? keyof NonNullable +type Ignore = X extends { [ignoreSymbol]?: infer Obj } ? keyof NonNullable : never type ExtractTypes< - X extends { - [typeSymbol]?: any - [unifySymbol]?: any - } -> = X extends any ? [ - NonNullable, - Ignore - ] + X +> = X extends { + [typeSymbol]?: infer _Type + [unifySymbol]?: infer _Unify +} ? [NonNullable<_Unify>, Ignore] : never type FilterIn = A extends any ? typeSymbol extends keyof A ? A : never : never From cde84e83c9ab02868a7d59e56b99e72ba6154973 Mon Sep 17 00:00:00 2001 From: Maxim Khramtsov Date: Wed, 25 Sep 2024 04:09:09 +0200 Subject: [PATCH 07/12] `Pool` is subtype of `Effect` (#3674) Co-authored-by: maksim.khramtsov Co-authored-by: Tim --- .changeset/lemon-worms-return.md | 5 +++++ packages/effect/dtslint/Unify.ts | 13 ++++++++++++- packages/effect/src/Pool.ts | 27 ++++++++++++++++++++++++++- packages/effect/src/internal/pool.ts | 8 +++++++- packages/effect/test/Pool.test.ts | 10 ++++++++++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 .changeset/lemon-worms-return.md diff --git a/.changeset/lemon-worms-return.md b/.changeset/lemon-worms-return.md new file mode 100644 index 0000000000..75a5d4fea4 --- /dev/null +++ b/.changeset/lemon-worms-return.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +`Pool` is now a subtype of `Effect`, equivalent to `Pool.get` diff --git a/packages/effect/dtslint/Unify.ts b/packages/effect/dtslint/Unify.ts index 92069fae68..c2a59db3ac 100644 --- a/packages/effect/dtslint/Unify.ts +++ b/packages/effect/dtslint/Unify.ts @@ -7,6 +7,7 @@ import type * as FiberRef from "effect/FiberRef" import type * as ManagedRuntime from "effect/ManagedRuntime" import type * as Micro from "effect/Micro" import type * as Option from "effect/Option" +import type * as Pool from "effect/Pool" import type * as Queue from "effect/Queue" import type * as RcRef from "effect/RcRef" import type * as Ref from "effect/Ref" @@ -116,6 +117,13 @@ export type DequeueUnify = Unify.Unify< | Queue.Dequeue<1> | Queue.Dequeue<"a"> > +// $ExpectType Pool<1, 2> | Pool<"a", "b" | "c"> +export type PoolUnify = Unify.Unify< + | Pool.Pool<1, 2> + | Pool.Pool<"a", "b"> + | Pool.Pool<"a", "c"> +> + // $ExpectType ScopedRef<1> | ScopedRef<"a"> export type ScopedRefUnify = Unify.Unify< | ScopedRef.ScopedRef<1> @@ -130,7 +138,7 @@ export type ResourceUnify = Unify.Unify< | Resource.Resource > -// $ExpectType 0 | Option | Ref<1> | Ref<"a"> | SynchronizedRef<1> | SynchronizedRef<"a"> | SubscriptionRef<1> | SubscriptionRef<"a"> | RcRef<"a", "b"> | Deferred<"a", "b"> | FiberRef<1> | FiberRef<"a"> | ManagedRuntime<"a", "b"> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | ScopedRef<1> | ScopedRef<"a"> | Resource<"a", "b"> | RcRef<1, 0> | Deferred<1, 0> | Resource<1, 0> | Latch | ManagedRuntime<1, 0> | Fiber<"a" | 1, 0 | "b"> | RuntimeFiber<"a" | 1, 0 | "b"> | Either<"a" | 1, 0 | "b"> | Effect<"a" | 1, 0 | "b", "R" | "R1"> +// $ExpectType 0 | Option | Ref<1> | Ref<"a"> | SynchronizedRef<1> | SynchronizedRef<"a"> | SubscriptionRef<1> | SubscriptionRef<"a"> | RcRef<"a", "b"> | Deferred<"a", "b"> | FiberRef<1> | FiberRef<"a"> | ManagedRuntime<"a", "b"> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | Pool<1, 2> | Pool<"a", "b" | "c"> | ScopedRef<1> | ScopedRef<"a"> | Resource<"a", "b"> | RcRef<1, 0> | Deferred<1, 0> | Resource<1, 0> | Latch | ManagedRuntime<1, 0> | Fiber<"a" | 1, 0 | "b"> | RuntimeFiber<"a" | 1, 0 | "b"> | Either<"a" | 1, 0 | "b"> | Effect<"a" | 1, 0 | "b", "R" | "R1"> export type AllUnify = Unify.Unify< | Either.Either<1, 0> | Either.Either<"a", "b"> @@ -158,6 +166,9 @@ export type AllUnify = Unify.Unify< | Queue.Queue<"a"> | Queue.Dequeue<1> | Queue.Dequeue<"a"> + | Pool.Pool<1, 2> + | Pool.Pool<"a", "b"> + | Pool.Pool<"a", "c"> | ScopedRef.ScopedRef<1> | ScopedRef.ScopedRef<"a"> | Resource.Resource<1, 0> diff --git a/packages/effect/src/Pool.ts b/packages/effect/src/Pool.ts index 59383ee57f..fd705d28c9 100644 --- a/packages/effect/src/Pool.ts +++ b/packages/effect/src/Pool.ts @@ -7,6 +7,7 @@ import * as internal from "./internal/pool.js" import type { Pipeable } from "./Pipeable.js" import type * as Scope from "./Scope.js" import type * as Types from "./Types.js" +import type * as Unify from "./Unify.js" /** * @since 2.0.0 @@ -28,7 +29,7 @@ export type PoolTypeId = typeof PoolTypeId * @since 2.0.0 * @category models */ -export interface Pool extends Pool.Variance, Pipeable { +export interface Pool extends Pool.Variance, Effect.Effect, Pipeable { /** * Retrieves an item from the pool in a scoped effect. Note that if * acquisition fails, then the returned effect will fail for that same reason. @@ -42,6 +43,30 @@ export interface Pool extends Pool.Variance, Pipe * than eagerly. */ invalidate(item: A): Effect.Effect + + readonly [Unify.typeSymbol]?: unknown + readonly [Unify.unifySymbol]?: PoolUnify + readonly [Unify.ignoreSymbol]?: PoolUnifyIgnore +} + +/** + * @category models + * @since 3.9.0 + */ +export interface PoolUnify extends Effect.EffectUnify { + Pool?: () => Extract> extends Pool | infer _ ? + A0 extends any ? Extract> extends Pool ? Pool + : never + : never : + never +} + +/** + * @category models + * @since 3.9.0 + */ +export interface PoolUnifyIgnore extends Effect.EffectUnifyIgnore { + Effect?: true } /** diff --git a/packages/effect/src/internal/pool.ts b/packages/effect/src/internal/pool.ts index 8079a98fb6..d32b91beb2 100644 --- a/packages/effect/src/internal/pool.ts +++ b/packages/effect/src/internal/pool.ts @@ -2,6 +2,7 @@ import type { Cause } from "effect/Cause" import * as Context from "../Context.js" import * as Duration from "../Duration.js" import type { Effect, Semaphore } from "../Effect.js" +import * as Effectable from "../Effectable.js" import type { Exit } from "../Exit.js" import { dual, identity } from "../Function.js" import * as Iterable from "../Iterable.js" @@ -122,7 +123,7 @@ interface Strategy { readonly reclaim: (pool: PoolImpl) => Effect>> } -class PoolImpl implements Pool { +class PoolImpl extends Effectable.Class implements Pool { readonly [PoolTypeId]: Pool.Variance[PoolTypeId_] isShuttingDown = false @@ -140,6 +141,7 @@ class PoolImpl implements Pool { readonly strategy: Strategy, readonly targetUtilization: number ) { + super() this[PoolTypeId] = poolVariance this.semaphore = circular.unsafeMakeSemaphore(concurrency * maxSize) } @@ -253,6 +255,10 @@ class PoolImpl implements Pool { ) ) + commit() { + return this.get + } + readonly get: Effect = core.flatMap( core.suspend(() => this.isShuttingDown ? core.interrupt : this.getPoolItem), (_) => _.exit diff --git a/packages/effect/test/Pool.test.ts b/packages/effect/test/Pool.test.ts index 756a838b34..c0de074820 100644 --- a/packages/effect/test/Pool.test.ts +++ b/packages/effect/test/Pool.test.ts @@ -423,4 +423,14 @@ describe("Pool", () => { expect(yield* Ref.get(allocations)).toBe(11) expect(yield* Ref.get(released)).toBe(11) })) + + it.scoped("is subtype of Effect", () => + Effect.gen(function*() { + const pool = yield* Pool.make({ + acquire: Effect.succeed(1), + size: 1 + }) + const item = yield* pool + assert.strictEqual(item, 1) + })) }) From 9252d3a88e33b824a739cb511d455ef961f4e02d Mon Sep 17 00:00:00 2001 From: Leo Conforti Date: Wed, 25 Sep 2024 14:13:29 +0000 Subject: [PATCH 08/12] add effectful methods for vitest hooks --- packages/vitest/src/index.ts | 48 +++++++++++++++++++++- packages/vitest/src/internal.ts | 73 +++++++++++++++++++++++++++------ 2 files changed, 107 insertions(+), 14 deletions(-) diff --git a/packages/vitest/src/index.ts b/packages/vitest/src/index.ts index e1fee6e0a2..fe11aa3bce 100644 --- a/packages/vitest/src/index.ts +++ b/packages/vitest/src/index.ts @@ -73,6 +73,42 @@ export const live: Vitest.Tester = internal.live */ export const scopedLive: Vitest.Tester = internal.scopedLive +/** + * @since 1.1.0 + */ +export const beforeAllEffect: ( + self: ( + suite: Readonly + ) => Effect.Effect, E, never> +) => void = internal.beforeAll + +/** + * @since 1.1.0 + */ +export const beforeEachEffect: ( + self: ( + ctx: V.TaskContext | V.RunnerTestCase> & V.TestContext & object, + suite: V.RunnerTestSuite + ) => Effect.Effect, E, never> +) => void = internal.beforeEach + +/** + * @since 1.1.0 + */ +export const afterAllEffect: ( + self: (suite: Readonly) => Effect.Effect, E, never> +) => void = internal.afterAll + +/** + * @since 1.1.0 + */ +export const afterEachEffect: ( + self: ( + ctx: V.TaskContext | V.RunnerTestCase> & V.TestContext & object, + suite: V.RunnerTestSuite + ) => Effect.Effect, E, never> +) => void = internal.afterEach + /** * @since 1.0.0 */ @@ -82,7 +118,17 @@ export const flakyTest: ( ) => Effect.Effect = internal.flakyTest /** @ignored */ -const methods = { effect, live, flakyTest, scoped, scopedLive } as const +const methods = { + effect, + live, + flakyTest, + scoped, + scopedLive, + beforeAllEffect, + beforeEachEffect, + afterAllEffect, + afterEachEffect +} as const /** * @since 1.0.0 diff --git a/packages/vitest/src/internal.ts b/packages/vitest/src/internal.ts index 983f073c4d..a8a6dec638 100644 --- a/packages/vitest/src/internal.ts +++ b/packages/vitest/src/internal.ts @@ -11,6 +11,7 @@ import * as Fiber from "effect/Fiber" import { flow, identity, pipe } from "effect/Function" import * as Layer from "effect/Layer" import * as Logger from "effect/Logger" +import type * as ManagedRuntime from "effect/ManagedRuntime" import * as Runtime from "effect/Runtime" import * as Schedule from "effect/Schedule" import type * as Scope from "effect/Scope" @@ -20,6 +21,29 @@ import * as Utils from "effect/Utils" import * as V from "vitest" import type * as Vitest from "./index.js" +/** @internal */ +const handleExit = (exit: Exit.Exit): Effect.Effect<() => void, never, never> => + Effect.gen(function*() { + if (Exit.isSuccess(exit)) { + return () => {} + } else { + const errors = Cause.prettyErrors(exit.cause) + for (let i = 1; i < errors.length; i++) { + yield* Effect.logError(errors[i]) + } + return () => { + throw errors[0] + } + } + }) + +/** @internal */ +const runHook = (effect: Effect.Effect) => + Effect.gen(function*() { + const exit = yield* Effect.exit(effect) + return yield* handleExit(exit) + }).pipe(Effect.runPromise).then((f) => f()) + /** @internal */ const runTest = (ctx: Vitest.TaskContext) => (effect: Effect.Effect) => Effect.gen(function*() { @@ -34,17 +58,7 @@ const runTest = (ctx: Vitest.TaskContext) => (effect: Effect.Effect) ) const exit = yield* Fiber.join(exitFiber) - if (Exit.isSuccess(exit)) { - return () => {} - } else { - const errors = Cause.prettyErrors(exit.cause) - for (let i = 1; i < errors.length; i++) { - yield* Effect.logError(errors[i]) - } - return () => { - throw errors[0] - } - } + return yield* handleExit(exit) }).pipe(Effect.runPromise).then((f) => f()) /** @internal */ @@ -69,8 +83,36 @@ export const addEqualityTesters = () => { } /** @internal */ -const makeTester = ( - mapEffect: (self: Effect.Effect) => Effect.Effect +export const beforeAll = ( + self: ( + suite: Readonly + ) => Effect.Effect, E, never> +): void => V.beforeAll((suite) => runHook(self(suite))) + +/** @internal */ +export const beforeEach = ( + self: ( + ctx: V.TaskContext | V.RunnerTestCase> & V.TestContext & object, + suite: V.RunnerTestSuite + ) => Effect.Effect, E, never> +): void => V.beforeEach((ctx, suite) => runHook(self(ctx, suite))) + +/** @internal */ +export const afterAll = ( + self: (suite: Readonly) => Effect.Effect, E, never> +): void => V.afterAll((suite) => runHook(self(suite))) + +/** @internal */ +export const afterEach = ( + self: ( + ctx: V.TaskContext | V.RunnerTestCase> & V.TestContext & object, + suite: V.RunnerTestSuite + ) => Effect.Effect, E, never> +): void => V.afterEach((ctx, suite) => runHook(self(ctx, suite))) + +/** @internal */ +const makeTester = ( + mapEffect: (self: Effect.Effect) => Effect.Effect ): Vitest.Vitest.Tester => { const run = >( ctx: V.TaskContext> & V.TestContext & object, @@ -110,6 +152,11 @@ export const live = makeTester(identity) /** @internal */ export const scopedLive = makeTester(Effect.scoped) +/** @internal */ +export const withManagedRuntime = ( + managedRuntime: ManagedRuntime.ManagedRuntime +): Vitest.Vitest.Tester => makeTester(Effect.provide(managedRuntime)) + /** @internal */ export const flakyTest = ( self: Effect.Effect, From 5f5e6a33a769c54763bdd8bfb550f1d8ae8f38a2 Mon Sep 17 00:00:00 2001 From: Leo Conforti Date: Wed, 25 Sep 2024 09:13:55 -0500 Subject: [PATCH 09/12] Create fluffy-insects-hug.md --- .changeset/fluffy-insects-hug.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fluffy-insects-hug.md diff --git a/.changeset/fluffy-insects-hug.md b/.changeset/fluffy-insects-hug.md new file mode 100644 index 0000000000..8e13132ee1 --- /dev/null +++ b/.changeset/fluffy-insects-hug.md @@ -0,0 +1,5 @@ +--- +"@effect/vitest": patch +--- + +Add effectful methods for vitest hooks From 3462af440d8e55e109d7720070aada9b0b6ad5e2 Mon Sep 17 00:00:00 2001 From: Leo Conforti Date: Wed, 25 Sep 2024 09:14:38 -0500 Subject: [PATCH 10/12] Update fluffy-insects-hug.md --- .changeset/fluffy-insects-hug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fluffy-insects-hug.md b/.changeset/fluffy-insects-hug.md index 8e13132ee1..4564a1bfc0 100644 --- a/.changeset/fluffy-insects-hug.md +++ b/.changeset/fluffy-insects-hug.md @@ -1,5 +1,5 @@ --- -"@effect/vitest": patch +"@effect/vitest": minor --- Add effectful methods for vitest hooks From 4fd9392de6e7fb69097490bde1f2db9594e1a0a1 Mon Sep 17 00:00:00 2001 From: Leo Conforti Date: Wed, 25 Sep 2024 14:33:44 +0000 Subject: [PATCH 11/12] add test --- packages/vitest/src/index.ts | 52 ++++++++++++++++++------------ packages/vitest/test/index.test.ts | 9 ++++++ 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/packages/vitest/src/index.ts b/packages/vitest/src/index.ts index fe11aa3bce..b0ff5d7563 100644 --- a/packages/vitest/src/index.ts +++ b/packages/vitest/src/index.ts @@ -3,6 +3,7 @@ */ import type * as Duration from "effect/Duration" import type * as Effect from "effect/Effect" +import type * as ManagedRuntime from "effect/ManagedRuntime" import type * as Scope from "effect/Scope" import type * as TestServices from "effect/TestServices" import * as V from "vitest" @@ -48,30 +49,11 @@ export namespace Vitest { ) => (name: string, self: TestFunction>, timeout?: number | V.TestOptions) => void } } -/** - * @since 1.0.0 - */ -export const addEqualityTesters: () => void = internal.addEqualityTesters - -/** - * @since 1.0.0 - */ -export const effect: Vitest.Tester = internal.effect /** * @since 1.0.0 */ -export const scoped: Vitest.Tester = internal.scoped - -/** - * @since 1.0.0 - */ -export const live: Vitest.Tester = internal.live - -/** - * @since 1.0.0 - */ -export const scopedLive: Vitest.Tester = internal.scopedLive +export const addEqualityTesters: () => void = internal.addEqualityTesters /** * @since 1.1.0 @@ -109,6 +91,33 @@ export const afterEachEffect: ( ) => Effect.Effect, E, never> ) => void = internal.afterEach +/** + * @since 1.0.0 + */ +export const effect: Vitest.Tester = internal.effect + +/** + * @since 1.0.0 + */ +export const scoped: Vitest.Tester = internal.scoped + +/** + * @since 1.0.0 + */ +export const live: Vitest.Tester = internal.live + +/** + * @since 1.0.0 + */ +export const scopedLive: Vitest.Tester = internal.scopedLive + +/** + * @since 1.1.0 + */ +export const withManagedRuntime: ( + managedRuntime: ManagedRuntime.ManagedRuntime +) => Vitest.Tester = internal.withManagedRuntime + /** * @since 1.0.0 */ @@ -127,7 +136,8 @@ const methods = { beforeAllEffect, beforeEachEffect, afterAllEffect, - afterEachEffect + afterEachEffect, + withManagedRuntime } as const /** diff --git a/packages/vitest/test/index.test.ts b/packages/vitest/test/index.test.ts index 6574aedec9..87401a80dd 100644 --- a/packages/vitest/test/index.test.ts +++ b/packages/vitest/test/index.test.ts @@ -1,6 +1,15 @@ import { expect, it } from "@effect/vitest" import { Effect } from "effect" +// hooks + +it.beforeAllEffect(() => Effect.void) +it.beforeEachEffect(() => Effect.void) +it.afterEachEffect(() => Effect.void) +it.afterAllEffect(() => Effect.void) + +// effects + it.live( "live %s", () => Effect.sync(() => expect(1).toEqual(1)) From 082cee76f25d91203681b960c326932ac1c2dbdb Mon Sep 17 00:00:00 2001 From: Leo Conforti Date: Wed, 25 Sep 2024 14:37:56 +0000 Subject: [PATCH 12/12] add timeouts --- packages/vitest/src/index.ts | 12 ++++++++---- packages/vitest/src/internal.ts | 20 ++++++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/vitest/src/index.ts b/packages/vitest/src/index.ts index b0ff5d7563..a2479ef887 100644 --- a/packages/vitest/src/index.ts +++ b/packages/vitest/src/index.ts @@ -61,7 +61,8 @@ export const addEqualityTesters: () => void = internal.addEqualityTesters export const beforeAllEffect: ( self: ( suite: Readonly - ) => Effect.Effect, E, never> + ) => Effect.Effect, E, never>, + timeout?: number ) => void = internal.beforeAll /** @@ -71,14 +72,16 @@ export const beforeEachEffect: ( self: ( ctx: V.TaskContext | V.RunnerTestCase> & V.TestContext & object, suite: V.RunnerTestSuite - ) => Effect.Effect, E, never> + ) => Effect.Effect, E, never>, + timeout?: number ) => void = internal.beforeEach /** * @since 1.1.0 */ export const afterAllEffect: ( - self: (suite: Readonly) => Effect.Effect, E, never> + self: (suite: Readonly) => Effect.Effect, E, never>, + timeout?: number ) => void = internal.afterAll /** @@ -88,7 +91,8 @@ export const afterEachEffect: ( self: ( ctx: V.TaskContext | V.RunnerTestCase> & V.TestContext & object, suite: V.RunnerTestSuite - ) => Effect.Effect, E, never> + ) => Effect.Effect, E, never>, + timeout?: number ) => void = internal.afterEach /** diff --git a/packages/vitest/src/internal.ts b/packages/vitest/src/internal.ts index a8a6dec638..1d8933dce6 100644 --- a/packages/vitest/src/internal.ts +++ b/packages/vitest/src/internal.ts @@ -86,29 +86,33 @@ export const addEqualityTesters = () => { export const beforeAll = ( self: ( suite: Readonly - ) => Effect.Effect, E, never> -): void => V.beforeAll((suite) => runHook(self(suite))) + ) => Effect.Effect, E, never>, + timeout?: number +): void => V.beforeAll((suite) => runHook(self(suite)), timeout) /** @internal */ export const beforeEach = ( self: ( ctx: V.TaskContext | V.RunnerTestCase> & V.TestContext & object, suite: V.RunnerTestSuite - ) => Effect.Effect, E, never> -): void => V.beforeEach((ctx, suite) => runHook(self(ctx, suite))) + ) => Effect.Effect, E, never>, + timeout?: number +): void => V.beforeEach((ctx, suite) => runHook(self(ctx, suite)), timeout) /** @internal */ export const afterAll = ( - self: (suite: Readonly) => Effect.Effect, E, never> -): void => V.afterAll((suite) => runHook(self(suite))) + self: (suite: Readonly) => Effect.Effect, E, never>, + timeout?: number +): void => V.afterAll((suite) => runHook(self(suite)), timeout) /** @internal */ export const afterEach = ( self: ( ctx: V.TaskContext | V.RunnerTestCase> & V.TestContext & object, suite: V.RunnerTestSuite - ) => Effect.Effect, E, never> -): void => V.afterEach((ctx, suite) => runHook(self(ctx, suite))) + ) => Effect.Effect, E, never>, + timeout?: number +): void => V.afterEach((ctx, suite) => runHook(self(ctx, suite)), timeout) /** @internal */ const makeTester = (