From 60259bd692e5a1c6e134fe59e6249a03915170e7 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 28 Feb 2024 23:23:51 +0100 Subject: [PATCH] add Rx.subRef & runtime.subRef constructor (#110) --- .changeset/nervous-ads-develop.md | 5 + docs/rx-react/index.ts.md | 18 +-- docs/rx/Result.ts.md | 99 +++++++++------- docs/rx/Rx.ts.md | 186 +++++++++++++++++------------- packages/rx/src/Rx.ts | 88 +++++++++++++- packages/rx/test/Rx.test.ts | 37 +++++- 6 files changed, 299 insertions(+), 134 deletions(-) create mode 100644 .changeset/nervous-ads-develop.md diff --git a/.changeset/nervous-ads-develop.md b/.changeset/nervous-ads-develop.md new file mode 100644 index 0000000..2d43526 --- /dev/null +++ b/.changeset/nervous-ads-develop.md @@ -0,0 +1,5 @@ +--- +"@effect-rx/rx": patch +--- + +add Rx.subRef & runtime.subRef constructor diff --git a/docs/rx-react/index.ts.md b/docs/rx-react/index.ts.md index 3feb65c..b42d11c 100644 --- a/docs/rx-react/index.ts.md +++ b/docs/rx-react/index.ts.md @@ -95,7 +95,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const useRxRefresh: (rx: any) => () => void +export declare const useRxRefresh: (rx: Rx.Rx & Rx.Refreshable) => () => void ``` Added in v1.0.0 @@ -116,8 +116,8 @@ Added in v1.0.0 ```ts export declare const useRxSetPromise: ( - rx: Rx.Writable, W> -) => (_: W) => Promise> + rx: Rx.Writable, W> +) => (_: W) => Promise> ``` Added in v1.0.0 @@ -141,10 +141,10 @@ Added in v1.0.0 **Signature** ```ts -export declare const useRxSuspense: ( - rx: Rx.Rx>, +export declare const useRxSuspense: ( + rx: Rx.Rx>, options?: { readonly suspendOnWaiting?: boolean } -) => any +) => Result.Success | Result.Failure ``` Added in v1.0.0 @@ -154,10 +154,10 @@ Added in v1.0.0 **Signature** ```ts -export declare const useRxSuspenseSuccess: ( - rx: Rx.Rx>, +export declare const useRxSuspenseSuccess: ( + rx: Rx.Rx>, options?: { readonly suspendOnWaiting?: boolean } -) => Result.Success +) => Result.Success ``` Added in v1.0.0 diff --git a/docs/rx/Result.ts.md b/docs/rx/Result.ts.md index 0b0b5eb..55098e8 100644 --- a/docs/rx/Result.ts.md +++ b/docs/rx/Result.ts.md @@ -43,6 +43,7 @@ Added in v1.0.0 - [refinements](#refinements) - [isFailure](#isfailure) - [isInitial](#isinitial) + - [isInterrupted](#isinterrupted) - [isNotInitial](#isnotinitial) - [isSuccess](#issuccess) - [type ids](#type-ids) @@ -58,7 +59,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const cause: (self: Result) => Option.Option> +export declare const cause: (self: Result) => Option.Option> ``` Added in v1.0.0 @@ -68,7 +69,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const value: (self: Result) => Option.Option +export declare const value: (self: Result) => Option.Option ``` Added in v1.0.0 @@ -80,8 +81,10 @@ Added in v1.0.0 **Signature** ```ts -export declare const map: ((f: (a: A) => B) => (self: Result) => Result) & - ((self: Result, f: (a: A) => B) => Result) +export declare const map: { + (f: (a: A) => B): (self: Result) => Result + (self: Result, f: (a: A) => B): Result +} ``` Added in v1.0.0 @@ -91,7 +94,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const toExit: (self: Result) => Exit.Exit +export declare const toExit: (self: Result) => Exit.Exit ``` Added in v1.0.0 @@ -103,11 +106,11 @@ Added in v1.0.0 **Signature** ```ts -export declare const fail: ( +export declare const fail: ( error: E, previousData?: Option.Option | undefined, waiting?: boolean -) => Failure +) => Failure ``` Added in v1.0.0 @@ -117,11 +120,11 @@ Added in v1.0.0 **Signature** ```ts -export declare const failWithPrevious: ( +export declare const failWithPrevious: ( error: E, - previous: Option.Option>, + previous: Option.Option>, waiting?: boolean -) => Failure +) => Failure ``` Added in v1.0.0 @@ -131,11 +134,11 @@ Added in v1.0.0 **Signature** ```ts -export declare const failure: ( +export declare const failure: ( cause: Cause.Cause, previousValue?: Option.Option, waiting?: boolean -) => Failure +) => Failure ``` Added in v1.0.0 @@ -145,11 +148,11 @@ Added in v1.0.0 **Signature** ```ts -export declare const failureWithPrevious: ( +export declare const failureWithPrevious: ( cause: Cause.Cause, - previous: Option.Option>, + previous: Option.Option>, waiting?: boolean -) => Failure +) => Failure ``` Added in v1.0.0 @@ -159,7 +162,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const fromExit: (exit: Exit.Exit) => Success | Failure +export declare const fromExit: (exit: Exit.Exit) => Success | Failure ``` Added in v1.0.0 @@ -169,10 +172,10 @@ Added in v1.0.0 **Signature** ```ts -export declare const fromExitWithPrevious: ( - exit: Exit.Exit, - previous: Option.Option> -) => Success | Failure +export declare const fromExitWithPrevious: ( + exit: Exit.Exit, + previous: Option.Option> +) => Success | Failure ``` Added in v1.0.0 @@ -182,7 +185,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const initial: (waiting?: boolean) => Initial +export declare const initial: (waiting?: boolean) => Initial ``` Added in v1.0.0 @@ -194,8 +197,8 @@ Added in v1.0.0 ```ts export declare const replacePrevious: , XE, A>( self: R, - previous: Option.Option> -) => Result.With, A> + previous: Option.Option> +) => Result.With> ``` Added in v1.0.0 @@ -205,7 +208,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const success: (value: A, waiting?: boolean) => Success +export declare const success: (value: A, waiting?: boolean) => Success ``` Added in v1.0.0 @@ -225,7 +228,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const waitingFrom: (previous: Option.Option>) => Result +export declare const waitingFrom: (previous: Option.Option>) => Result ``` Added in v1.0.0 @@ -237,7 +240,7 @@ Added in v1.0.0 **Signature** ```ts -export interface Failure extends Result.Proto { +export interface Failure extends Result.Proto { readonly _tag: "Failure" readonly cause: Cause.Cause readonly previousValue: Option.Option @@ -251,7 +254,7 @@ Added in v1.0.0 **Signature** ```ts -export interface Initial extends Result.Proto { +export interface Initial extends Result.Proto { readonly _tag: "Initial" } ``` @@ -263,7 +266,7 @@ Added in v1.0.0 **Signature** ```ts -export type Result = Initial | Success | Failure +export type Result = Initial | Success | Failure ``` Added in v1.0.0 @@ -277,7 +280,7 @@ Added in v1.0.0 **Signature** ```ts -export interface Proto extends Pipeable, Data.Case { +export interface Proto extends Pipeable { readonly [TypeId]: { readonly E: (_: never) => E readonly A: (_: never) => A @@ -293,7 +296,7 @@ Added in v1.0.0 **Signature** ```ts -export type InferA> = R extends Result ? A : never +export type InferA> = R extends Result ? A : never ``` Added in v1.0.0 @@ -303,7 +306,7 @@ Added in v1.0.0 **Signature** ```ts -export type InferE> = R extends Result ? E : never +export type InferE> = R extends Result ? E : never ``` Added in v1.0.0 @@ -313,13 +316,13 @@ Added in v1.0.0 **Signature** ```ts -export type With, E, A> = - R extends Initial - ? Initial - : R extends Success - ? Success - : R extends Failure - ? Failure +export type With, A, E> = + R extends Initial + ? Initial + : R extends Success + ? Success + : R extends Failure + ? Failure : never ``` @@ -330,7 +333,7 @@ Added in v1.0.0 **Signature** ```ts -export interface Success extends Result.Proto { +export interface Success extends Result.Proto { readonly _tag: "Success" readonly value: A } @@ -345,7 +348,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const isFailure: (result: Result) => result is Failure +export declare const isFailure: (result: Result) => result is Failure ``` Added in v1.0.0 @@ -355,7 +358,17 @@ Added in v1.0.0 **Signature** ```ts -export declare const isInitial: (result: Result) => result is Initial +export declare const isInitial: (result: Result) => result is Initial +``` + +Added in v1.0.0 + +## isInterrupted + +**Signature** + +```ts +export declare const isInterrupted: (result: Result) => result is Failure ``` Added in v1.0.0 @@ -365,7 +378,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const isNotInitial: (result: Result) => result is Success | Failure +export declare const isNotInitial: (result: Result) => result is Success | Failure ``` Added in v1.0.0 @@ -375,7 +388,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const isSuccess: (result: Result) => result is Success +export declare const isSuccess: (result: Result) => result is Success ``` Added in v1.0.0 diff --git a/docs/rx/Rx.ts.md b/docs/rx/Rx.ts.md index a91f92f..8c5abc8 100644 --- a/docs/rx/Rx.ts.md +++ b/docs/rx/Rx.ts.md @@ -31,6 +31,7 @@ Added in v1.0.0 - [make](#make) - [pull](#pull) - [readable](#readable) + - [subRef](#subref) - [writable](#writable) - [context](#context-1) - [Context (interface)](#context-interface) @@ -127,19 +128,21 @@ Added in v1.0.0 **Signature** ```ts -export declare const mapResult: (>, B>( - f: (_: Result.Result.InferA>) => B -) => ( - self: R -) => [R] extends [Writable] - ? Writable>, B>, RW> - : Rx>, B>>) & - (>, B>( - self: R, +export declare const mapResult: { + >, B>( f: (_: Result.Result.InferA>) => B + ): ( + self: R ) => [R] extends [Writable] - ? Writable>, B>, RW> - : Rx>, B>>) + ? Writable>>, RW> + : Rx>>> + >, B>( + self: R, + f: (_: Result.Result.InferA>) => B + ): [R] extends [Writable] + ? Writable>>, RW> + : Rx>>> +} ``` Added in v1.0.0 @@ -174,18 +177,18 @@ Added in v1.0.0 ```ts export declare const withFallback: { ( - fallback: Rx> + fallback: Rx> ): >>( self: R ) => [R] extends [Writable] - ? Writable>, A2 | Result.Result.InferA>>, RW> - : Rx>, A2 | Result.Result.InferA>>> - >, E2, A2>( + ? Writable>, E2 | Result.Result.InferE>>, RW> + : Rx>, E2 | Result.Result.InferE>>> + >, A2, E2>( self: R, - fallback: Rx> + fallback: Rx> ): [R] extends [Writable] - ? Writable>, A2 | Result.Result.InferA>>, RW> - : Rx>, A2 | Result.Result.InferA>>> + ? Writable>, E2 | Result.Result.InferE>>, RW> + : Rx>, E2 | Result.Result.InferE>>> } ``` @@ -211,9 +214,9 @@ Added in v1.0.0 **Signature** ```ts -export declare const context: () => ( - create: Layer.Layer | Rx.Read> -) => RxRuntime +export declare const context: () => ( + create: Layer.Layer | Rx.Read> +) => RxRuntime ``` Added in v1.0.0 @@ -235,13 +238,13 @@ Added in v1.0.0 ```ts export declare const fn: { ( - fn: Rx.ReadFn>, + fn: Rx.ReadFn>, options?: { readonly initialValue?: A | undefined } | undefined - ): RxResultFn extends true ? void : Arg> + ): RxResultFn extends true ? void : Arg, A, E> ( - fn: Rx.ReadFn>, + fn: Rx.ReadFn>, options?: { readonly initialValue?: A | undefined } | undefined - ): RxResultFn extends true ? void : Arg> + ): RxResultFn extends true ? void : Arg, A, E | NoSuchElementException> } ``` @@ -266,22 +269,22 @@ Added in v1.0.0 ```ts export declare const make: { - ( - effect: Effect.Effect, + ( + effect: Effect.Effect, options?: { readonly initialValue?: A | undefined } | undefined - ): Rx> - ( - create: Rx.Read>, + ): Rx> + ( + create: Rx.Read>, options?: { readonly initialValue?: A | undefined } | undefined - ): Rx> - ( - stream: Stream.Stream, + ): Rx> + ( + stream: Stream.Stream, options?: { readonly initialValue?: A | undefined } | undefined - ): Rx> - ( - create: Rx.Read>, + ): Rx> + ( + create: Rx.Read>, options?: { readonly initialValue?: A | undefined } | undefined - ): Rx> + ): Rx> (create: Rx.Read): Rx (initialValue: A): Writable } @@ -294,12 +297,12 @@ Added in v1.0.0 **Signature** ```ts -export declare const pull: ( - create: Stream.Stream | Rx.Read>, +export declare const pull: ( + create: Stream.Stream | Rx.Read>, options?: | { readonly disableAccumulation?: boolean | undefined; readonly initialValue?: readonly A[] | undefined } | undefined -) => Writable, void> +) => Writable, void> ``` Added in v1.0.0 @@ -314,6 +317,23 @@ export declare const readable: (read: Rx.Read, refresh?: Rx.Refresh) => Rx Added in v1.0.0 +## subRef + +**Signature** + +```ts +export declare const subRef: { + (ref: SubscriptionRef.SubscriptionRef | Rx.Read>): Writable + ( + effect: + | Effect.Effect, E, never> + | Rx.Read, E, never>> + ): Writable, A> +} +``` + +Added in v1.0.0 + ## writable **Signature** @@ -334,33 +354,33 @@ Added in v1.0.0 export interface Context { (rx: Rx): A readonly get: (rx: Rx) => A - readonly result: (rx: Rx>) => Exit.Exit + readonly result: (rx: Rx>) => Exit.Exit readonly once: (rx: Rx) => A readonly addFinalizer: (f: () => void) => void readonly mount: (rx: Rx) => void readonly refreshSync: (rx: Rx & Refreshable) => void - readonly refresh: (rx: Rx & Refreshable) => Effect.Effect + readonly refresh: (rx: Rx & Refreshable) => Effect.Effect readonly refreshSelfSync: () => void - readonly refreshSelf: Effect.Effect + readonly refreshSelf: Effect.Effect readonly self: () => Option.Option readonly setSelfSync: (a: A) => void - readonly setSelf: (a: A) => Effect.Effect + readonly setSelf: (a: A) => Effect.Effect readonly setSync: (rx: Writable, value: W) => void - readonly set: (rx: Writable, value: W) => Effect.Effect + readonly set: (rx: Writable, value: W) => Effect.Effect readonly stream: ( rx: Rx, options?: { readonly withoutInitialValue?: boolean readonly bufferSize?: number } - ) => Stream.Stream - readonly streamResult: ( - rx: Rx>, + ) => Stream.Stream + readonly streamResult: ( + rx: Rx>, options?: { readonly withoutInitialValue?: boolean readonly bufferSize?: number } - ) => Stream.Stream + ) => Stream.Stream readonly subscribe: ( rx: Rx, f: (_: A) => void, @@ -393,9 +413,9 @@ Added in v1.0.0 **Signature** ```ts -export declare const runtime: ( - create: Layer.Layer | Rx.Read> -) => RxRuntime +export declare const runtime: ( + create: Layer.Layer | Rx.Read> +) => RxRuntime ``` Added in v1.0.0 @@ -407,12 +427,12 @@ Added in v1.0.0 **Signature** ```ts -export type PullResult = Result.Result< - E | NoSuchElementException, +export type PullResult = Result.Result< { readonly done: boolean readonly items: Array - } + }, + E | NoSuchElementException > ``` @@ -466,7 +486,7 @@ Added in v1.0.0 **Signature** ```ts -export type GetResult = (rx: Rx>) => Exit.Exit +export type GetResult = (rx: Rx>) => Exit.Exit ``` Added in v1.0.0 @@ -526,7 +546,7 @@ Added in v1.0.0 **Signature** ```ts -export type RefreshRx = (rx: Rx & Refreshable) => Effect.Effect +export type RefreshRx = (rx: Rx & Refreshable) => Effect.Effect ``` Added in v1.0.0 @@ -556,7 +576,7 @@ Added in v1.0.0 **Signature** ```ts -export type SetEffect = (rx: Writable, value: W) => Effect.Effect +export type SetEffect = (rx: Writable, value: W) => Effect.Effect ``` Added in v1.0.0 @@ -592,7 +612,7 @@ Added in v1.0.0 **Signature** ```ts -export interface RxResultFn extends Writable, Arg> {} +export interface RxResultFn extends Writable, Arg> {} ``` Added in v1.0.0 @@ -602,58 +622,64 @@ Added in v1.0.0 **Signature** ```ts -export interface RxRuntime extends Rx>> { - readonly layer: Rx> +export interface RxRuntime extends Rx, ER>> { + readonly layer: Rx> readonly rx: { - ( - effect: Effect.Effect, + ( + effect: Effect.Effect, options?: { readonly initialValue?: A } - ): Rx> - ( - create: Rx.Read>, + ): Rx> + ( + create: Rx.Read>, options?: { readonly initialValue?: A } - ): Rx> - ( - stream: Stream.Stream, + ): Rx> + ( + stream: Stream.Stream, options?: { readonly initialValue?: A } - ): Rx> - ( - create: Rx.Read>, + ): Rx> + ( + create: Rx.Read>, options?: { readonly initialValue?: A } - ): Rx> + ): Rx> } readonly fn: { ( - fn: Rx.ReadFn>, + fn: Rx.ReadFn>, options?: { readonly initialValue?: A } - ): RxResultFn extends true ? void : Arg> + ): RxResultFn extends true ? void : Arg, A, E | ER> ( - fn: Rx.ReadFn>, + fn: Rx.ReadFn>, options?: { readonly initialValue?: A } - ): RxResultFn extends true ? void : Arg> + ): RxResultFn extends true ? void : Arg, A, E | ER | NoSuchElementException> } - readonly pull: ( - create: Rx.Read> | Stream.Stream, + readonly pull: ( + create: Rx.Read> | Stream.Stream, options?: { readonly disableAccumulation?: boolean readonly initialValue?: ReadonlyArray } - ) => Writable, void> + ) => Writable, void> + + readonly subRef: ( + create: + | Effect.Effect, E, R> + | Rx.Read, E, R>> + ) => Writable, A> } ``` diff --git a/packages/rx/src/Rx.ts b/packages/rx/src/Rx.ts index 595a9b1..37c0285 100644 --- a/packages/rx/src/Rx.ts +++ b/packages/rx/src/Rx.ts @@ -18,11 +18,11 @@ import { type Pipeable, pipeArguments } from "effect/Pipeable" import * as Runtime from "effect/Runtime" import * as Scope from "effect/Scope" import * as Stream from "effect/Stream" +import * as SubscriptionRef from "effect/SubscriptionRef" import type * as Types from "effect/Types" import * as internalRegistry from "./internal/registry.js" import { runCallbackSync } from "./internal/runtime.js" import * as Result from "./Result.js" - /** * @since 1.0.0 * @category type ids @@ -282,6 +282,22 @@ const RxRuntimeProto = { ) }) return makeStreamPull(pullRx as any, options) + }, + + subRef(this: RxRuntime, arg: any) { + return makeSubRef(readable((get) => { + const previous = get.self>() + const runtimeResult = get(this) + if (runtimeResult._tag !== "Success") { + return Result.replacePrevious(runtimeResult, previous) + } + return makeEffect( + get, + arg, + Result.initial(true), + runtimeResult.value + ) + })) } } @@ -515,6 +531,12 @@ export interface RxRuntime extends Rx, E readonly disableAccumulation?: boolean readonly initialValue?: ReadonlyArray }) => Writable, void> + + readonly subRef: ( + create: + | Effect.Effect, E, R> + | Rx.Read, E, R>> + ) => Writable, A> } /** @@ -614,6 +636,70 @@ function makeStream( return Result.waiting(initialValue) } +// ----------------------------------------------------------------------------- +// constructors - subscription ref +// ----------------------------------------------------------------------------- + +const makeSubRef = ( + refRx: Rx | Result.Result, any>> +) => { + function read(get: Context) { + const ref = get(refRx) + if (SubscriptionRef.SubscriptionRefTypeId in ref) { + get.addFinalizer( + ref.changes.pipe( + Stream.runForEach((value) => get.setSelf(value)), + Effect.runCallback + ) + ) + return Effect.runSync(SubscriptionRef.get(ref)) + } else if (ref._tag !== "Success") { + return ref + } + return makeStream(get, ref.value.changes, Result.initial(true)) + } + + function write(ctx: WriteContext>, value: any) { + const ref = ctx.get(refRx) + if (SubscriptionRef.SubscriptionRefTypeId in ref) { + Effect.runSync(SubscriptionRef.set(ref, value)) + } else if (Result.isSuccess(ref)) { + Effect.runSync(SubscriptionRef.set(ref.value, value)) + } + } + + return writable(read, write) +} + +/** + * @since 1.0.0 + * @category constructors + */ +export const subRef: { + (ref: SubscriptionRef.SubscriptionRef | Rx.Read>): Writable + ( + effect: + | Effect.Effect, E, never> + | Rx.Read, E, never>> + ): Writable, A> +} = ( + ref: + | SubscriptionRef.SubscriptionRef + | Rx.Read> + | Effect.Effect, any, never> + | Rx.Read, any, never>> +) => + makeSubRef(readable((get) => { + let value: SubscriptionRef.SubscriptionRef | Effect.Effect, any, any> + if (typeof ref === "function") { + value = ref(get) + } else { + value = ref + } + + return Effect.isEffect(value) ? makeEffect(get, value, Result.initial(true)) : value + })) + // ----------------------------------------------------------------------------- // constructors - functions // ----------------------------------------------------------------------------- diff --git a/packages/rx/test/Rx.test.ts b/packages/rx/test/Rx.test.ts index 333198e..f06506b 100644 --- a/packages/rx/test/Rx.test.ts +++ b/packages/rx/test/Rx.test.ts @@ -1,7 +1,7 @@ import * as Registry from "@effect-rx/rx/Registry" import * as Result from "@effect-rx/rx/Result" import * as Rx from "@effect-rx/rx/Rx" -import { Cause, Either, FiberRef } from "effect" +import { Cause, Either, FiberRef, SubscriptionRef } from "effect" import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as Hash from "effect/Hash" @@ -774,6 +774,41 @@ describe("Rx", () => { const r = Registry.make() assert.deepStrictEqual(r.get(rx), Either.right(123)) }) + + it("SubscriptionRef", async () => { + vitest.useRealTimers() + const ref = SubscriptionRef.make(0).pipe(Effect.runSync) + const rx = Rx.subRef(ref) + const r = Registry.make() + const unmount = r.mount(rx) + assert.deepStrictEqual(r.get(rx), 0) + r.set(rx, 1) + await new Promise((resolve) => resolve(null)) + assert.deepStrictEqual(r.get(rx), 1) + unmount() + }) + + it("SubscriptionRef/effect", async () => { + const rx = Rx.subRef(SubscriptionRef.make(0)) + const r = Registry.make() + const unmount = r.mount(rx) + assert.deepStrictEqual(r.get(rx), Result.success(0, true)) + r.set(rx, 1) + await new Promise((resolve) => resolve(null)) + assert.deepStrictEqual(r.get(rx), Result.success(1, true)) + unmount() + }) + + it("SubscriptionRef/runtime", async () => { + const rx = counterRuntime.subRef(SubscriptionRef.make(0)) + const r = Registry.make() + const unmount = r.mount(rx) + assert.deepStrictEqual(r.get(rx), Result.success(0, true)) + r.set(rx, 1) + await new Promise((resolve) => resolve(null)) + assert.deepStrictEqual(r.get(rx), Result.success(1, true)) + unmount() + }) }) interface BuildCounter {