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/.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/.changeset/fast-experts-melt.md b/.changeset/fast-experts-melt.md new file mode 100644 index 0000000000..fb610b129b --- /dev/null +++ b/.changeset/fast-experts-melt.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +fix Unify for Deferred diff --git a/.changeset/healthy-dogs-judge.md b/.changeset/healthy-dogs-judge.md new file mode 100644 index 0000000000..60164c55f0 --- /dev/null +++ b/.changeset/healthy-dogs-judge.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +Effect.mapAccum & Array.mapAccum preserve non-emptiness 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/.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/silly-glasses-deliver.md b/.changeset/silly-glasses-deliver.md new file mode 100644 index 0000000000..a27d0df79f --- /dev/null +++ b/.changeset/silly-glasses-deliver.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +move ManagedRuntime.TypeId to fix circular imports 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/.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/.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/Array.ts b/packages/effect/dtslint/Array.ts index 53ea255d1c..682abf120d 100644 --- a/packages/effect/dtslint/Array.ts +++ b/packages/effect/dtslint/Array.ts @@ -1504,3 +1504,24 @@ pipe( _n // $ExpectType 1 | 2 ) => "a" as const) ) + +// ------------------------------------------------------------------------------------- +// mapAccum +// ------------------------------------------------------------------------------------- + +// $ExpectType [state: number, mappedArray: string[]] +Array.mapAccum(strings, 0, (s, a, i) => [s + i, a]) + +// $ExpectType [state: number, mappedArray: [string, ...string[]]] +Array.mapAccum(nonEmptyReadonlyStrings, 0, (s, a, i) => [s + i, a]) + +// $ExpectType [state: number, mappedArray: string[]] +pipe( + strings, + Array.mapAccum(0, (s, a, i) => [s + i, a]) +) +// $ExpectType [state: number, mappedArray: [string, ...string[]]] +pipe( + nonEmptyReadonlyStrings, + Array.mapAccum(0, (s, a, i) => [s + i, a]) +) diff --git a/packages/effect/dtslint/Effect.ts b/packages/effect/dtslint/Effect.ts index caeaea1b59..27e1f45659 100644 --- a/packages/effect/dtslint/Effect.ts +++ b/packages/effect/dtslint/Effect.ts @@ -1263,3 +1263,28 @@ pipe( ) => "b" as const ) ) + +// ------------------------------------------------------------------------------------- +// mapAccum +// ------------------------------------------------------------------------------------- + +declare const nonEmptyReadonlyStrings: NonEmptyReadonlyArray +declare const strings: Array + +// $ExpectType Effect<[number, string[]], never, never> +Effect.mapAccum(strings, 0, (s, a, i) => Effect.succeed([s + i, a])) + +// $ExpectType Effect<[number, [string, ...string[]]], never, never> +Effect.mapAccum(nonEmptyReadonlyStrings, 0, (s, a, i) => Effect.succeed([s + i, a])) + +// $ExpectType Effect<[number, string[]], never, never> +pipe( + strings, + Effect.mapAccum(0, (s, a, i) => Effect.succeed([s + i, a])) +) + +// $ExpectType Effect<[number, [string, ...string[]]], never, never> +pipe( + nonEmptyReadonlyStrings, + Effect.mapAccum(0, (s, a, i) => Effect.succeed([s + i, a])) +) 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/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/dtslint/Unify.ts b/packages/effect/dtslint/Unify.ts index 8a122c7e87..32185fdbe6 100644 --- a/packages/effect/dtslint/Unify.ts +++ b/packages/effect/dtslint/Unify.ts @@ -4,11 +4,15 @@ 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 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" +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" @@ -98,7 +102,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> @@ -109,34 +117,64 @@ 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 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 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> | Ref<"a"> | SynchronizedRef<1> | SynchronizedRef<"a"> | SubscriptionRef<1> | SubscriptionRef<"a"> | 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"> | Deferred<1, 0> | Resource<1, 0> | Latch | ManagedRuntime<1, 0> | RcRef<"a" | 1, 0 | "b"> | 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"> | 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> + | Resource.Resource<"a", "b"> + | Effect.Latch + | ManagedRuntime.ManagedRuntime<1, 0> + | ManagedRuntime.ManagedRuntime<"a", "b"> | 0 > diff --git a/packages/effect/src/Array.ts b/packages/effect/src/Array.ts index 6867785277..334cbbfb19 100644 --- a/packages/effect/src/Array.ts +++ b/packages/effect/src/Array.ts @@ -2859,11 +2859,15 @@ export const join: { * @category folding */ export const mapAccum: { - ( + = Iterable>( s: S, - f: (s: S, a: A, i: number) => readonly [S, B] - ): (self: Iterable) => [state: S, mappedArray: Array] - (self: Iterable, s: S, f: (s: S, a: A, i: number) => readonly [S, B]): [state: S, mappedArray: Array] + f: (s: S, a: ReadonlyArray.Infer, i: number) => readonly [S, B] + ): (self: I) => [state: S, mappedArray: ReadonlyArray.With] + = Iterable>( + self: I, + s: S, + f: (s: S, a: ReadonlyArray.Infer, i: number) => readonly [S, B] + ): [state: S, mappedArray: ReadonlyArray.With] } = dual( 3, (self: Iterable, s: S, f: (s: S, a: A, i: number) => [S, B]): [state: S, mappedArray: Array] => { 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/Effect.ts b/packages/effect/src/Effect.ts index 090039deb2..f9929c4312 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" @@ -2364,15 +2365,15 @@ export const map: { * @category mapping */ export const mapAccum: { - ( + = Iterable>( zero: S, - f: (s: S, a: A, i: number) => Effect - ): (elements: Iterable) => Effect<[S, Array], E, R> - ( - elements: Iterable, + f: (s: S, a: RA.ReadonlyArray.Infer, i: number) => Effect + ): (elements: I) => Effect<[S, RA.ReadonlyArray.With], E, R> + = Iterable>( + elements: I, zero: S, - f: (s: S, a: A, i: number) => Effect - ): Effect<[S, Array], E, R> + f: (s: S, a: RA.ReadonlyArray.Infer, i: number) => Effect + ): Effect<[S, RA.ReadonlyArray.With], E, R> } = effect.mapAccum /** @@ -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 /** @@ -5408,7 +5416,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 +5427,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/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] } /** diff --git a/packages/effect/src/ManagedRuntime.ts b/packages/effect/src/ManagedRuntime.ts index d66b50dbd9..0ab054c5db 100644 --- a/packages/effect/src/ManagedRuntime.ts +++ b/packages/effect/src/ManagedRuntime.ts @@ -5,9 +5,32 @@ import type * as Effect from "./Effect.js" import type * as Exit from "./Exit.js" import type * as Fiber from "./Fiber.js" import * as internal from "./internal/managedRuntime.js" +import * as circular from "./internal/managedRuntime/circular.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 = circular.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 +52,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 +127,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/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/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/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/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/src/Resource.ts b/packages/effect/src/Resource.ts index dc9af2a864..1f9713e770 100644 --- a/packages/effect/src/Resource.ts +++ b/packages/effect/src/Resource.ts @@ -8,6 +8,7 @@ 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 +29,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 { /** @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/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 diff --git a/packages/effect/src/internal/core-effect.ts b/packages/effect/src/internal/core-effect.ts index 8dfc44b07d..5d3c929fc4 100644 --- a/packages/effect/src/internal/core-effect.ts +++ b/packages/effect/src/internal/core-effect.ts @@ -1055,17 +1055,17 @@ const loopDiscard = ( /* @internal */ export const mapAccum: { - ( + = Iterable>( zero: S, f: (s: S, a: A, i: number) => Effect.Effect - ): (elements: Iterable) => Effect.Effect<[S, Array], E, R> - ( - elements: Iterable, + ): (elements: I) => Effect.Effect<[S, Arr.ReadonlyArray.With], E, R> + = Iterable>( + elements: I, zero: S, f: (s: S, a: A, i: number) => Effect.Effect - ): Effect.Effect<[S, Array], E, R> -} = dual(3, ( - elements: Iterable, + ): Effect.Effect<[S, Arr.ReadonlyArray.With], E, R> +} = dual(3, = Iterable>( + elements: I, zero: S, f: (s: S, a: A, i: number) => Effect.Effect ): Effect.Effect<[S, Array], E, R> => 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/src/internal/layer.ts b/packages/effect/src/internal/layer.ts index ca60bd5414..7cf47e70a6 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" @@ -23,6 +24,7 @@ import * as effect from "./core-effect.js" import * as core from "./core.js" import * as circular from "./effect/circular.js" import * as fiberRuntime from "./fiberRuntime.js" +import * as circularManagedRuntime from "./managedRuntime/circular.js" import * as EffectOpCodes from "./opCodes/effect.js" import * as OpCodes from "./opCodes/layer.js" import * as ref from "./ref.js" @@ -1300,6 +1302,9 @@ export const effect_provide = dual< ( runtime: Runtime.Runtime ): (self: Effect.Effect) => Effect.Effect> + ( + managedRuntime: ManagedRuntime.ManagedRuntime + ): (self: Effect.Effect) => Effect.Effect> }, { ( @@ -1314,16 +1319,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 (circularManagedRuntime.TypeId 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..4a27cf77f9 100644 --- a/packages/effect/src/internal/managedRuntime.ts +++ b/packages/effect/src/internal/managedRuntime.ts @@ -1,22 +1,28 @@ 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" import * as core from "./core.js" import * as fiberRuntime from "./fiberRuntime.js" import * as internalLayer from "./layer.js" +import * as circular from "./managedRuntime/circular.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 isManagedRuntime = (u: unknown): u is M.ManagedRuntime => hasProperty(u, circular.TypeId) + function provide( managed: ManagedRuntimeImpl, effect: Effect.Effect @@ -32,34 +38,42 @@ function provide( ) } +const ManagedRuntimeProto = { + ...Effectable.CommitPrototype, + [circular.TypeId]: circular.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 +124,6 @@ export const make = ( internalRuntime.unsafeRunPromiseEffect(provide(self, effect), options) : internalRuntime.unsafeRunPromise(self.cachedRuntime)(effect, options) } - } + }) return self } diff --git a/packages/effect/src/internal/managedRuntime/circular.ts b/packages/effect/src/internal/managedRuntime/circular.ts new file mode 100644 index 0000000000..9abefc95c1 --- /dev/null +++ b/packages/effect/src/internal/managedRuntime/circular.ts @@ -0,0 +1,6 @@ +import type * as M from "../../ManagedRuntime.js" + +// circular with Layer + +/** @internal */ +export const TypeId: M.TypeId = Symbol.for("effect/ManagedRuntime") as M.TypeId 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/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/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) + })) }) 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") + }) + ) }) 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) + })) }) 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]> = [ 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) + })) })