From d5e5e03d1309aead228cdb4e56e5083a7b11ef42 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 19 Sep 2023 17:26:37 +1200 Subject: [PATCH] add function constructors --- .changeset/long-falcons-cover.md | 6 +++ docs/rx-react/index.ts.md | 25 +++------ docs/rx/Registry.ts.md | 1 + docs/rx/Rx.ts.md | 83 ++++++++++++++++++++++-------- packages/rx-react/src/index.ts | 29 +++++------ packages/rx/src/Rx.ts | 88 +++++++++++++++++++++++++++++++- packages/rx/test/Rx.test.ts | 41 +++++++++++++++ 7 files changed, 219 insertions(+), 54 deletions(-) create mode 100644 .changeset/long-falcons-cover.md diff --git a/.changeset/long-falcons-cover.md b/.changeset/long-falcons-cover.md new file mode 100644 index 0000000..663a57e --- /dev/null +++ b/.changeset/long-falcons-cover.md @@ -0,0 +1,6 @@ +--- +"@effect-rx/rx-react": patch +"@effect-rx/rx": patch +--- + +add function constructors diff --git a/docs/rx-react/index.ts.md b/docs/rx-react/index.ts.md index 16611c2..98898e6 100644 --- a/docs/rx-react/index.ts.md +++ b/docs/rx-react/index.ts.md @@ -15,11 +15,10 @@ Added in v1.0.0 - [context](#context) - [RegistryContext](#registrycontext) - [hooks](#hooks) + - [useRefreshRx](#userefreshrx) - [useRx](#userx) - - [useRxUpdate](#userxupdate) - [useRxValue](#userxvalue) - [useSetRx](#usesetrx) - - [useUpdateRx](#useupdaterx) --- @@ -37,22 +36,24 @@ Added in v1.0.0 # hooks -## useRx +## useRefreshRx **Signature** ```ts -export declare const useRx: (rx: Rx.Writeable) => readonly [R, (_: W) => void] +export declare const useRefreshRx: (rx: Rx.Rx & Rx.Refreshable) => () => void ``` Added in v1.0.0 -## useRxUpdate +## useRx **Signature** ```ts -export declare const useRxUpdate: (rx: Rx.Writeable) => readonly [R, (f: (_: R) => W) => void] +export declare const useRx: ( + rx: Rx.Writeable +) => readonly [value: R, setOrUpdate: (_: W | ((_: R) => W)) => void] ``` Added in v1.0.0 @@ -72,17 +73,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const useSetRx: (rx: Rx.Writeable) => (_: W) => void -``` - -Added in v1.0.0 - -## useUpdateRx - -**Signature** - -```ts -export declare const useUpdateRx: (rx: Rx.Writeable) => (f: (_: R) => W) => void +export declare const useSetRx: (rx: Rx.Writeable) => (_: W | ((_: R) => W)) => void ``` Added in v1.0.0 diff --git a/docs/rx/Registry.ts.md b/docs/rx/Registry.ts.md index 2a739f8..aa0f268 100644 --- a/docs/rx/Registry.ts.md +++ b/docs/rx/Registry.ts.md @@ -48,6 +48,7 @@ export interface Registry { readonly refresh: Rx.Rx.Refresh readonly set: Rx.Rx.Set readonly subscribe: Rx.Rx.Subscribe + readonly subscribeGetter: Rx.Rx.SubscribeGetter } ``` diff --git a/docs/rx/Rx.ts.md b/docs/rx/Rx.ts.md index 6156016..62a81cc 100644 --- a/docs/rx/Rx.ts.md +++ b/docs/rx/Rx.ts.md @@ -20,9 +20,12 @@ Added in v1.0.0 - [refreshable](#refreshable) - [constructors](#constructors) - [effect](#effect) + - [effectFn](#effectfn) + - [fn](#fn) - [readable](#readable) - [runtime](#runtime) - [scoped](#scoped) + - [scopedFn](#scopedfn) - [state](#state) - [writable](#writable) - [context](#context) @@ -37,12 +40,12 @@ Added in v1.0.0 - [Rx (namespace)](#rx-namespace) - [Get (type alias)](#get-type-alias) - [Mount (type alias)](#mount-type-alias) - - [Queue (type alias)](#queue-type-alias) - [Refresh (type alias)](#refresh-type-alias) - [Set (type alias)](#set-type-alias) - [Subscribe (type alias)](#subscribe-type-alias) - - [SubscribeWithPrevious (type alias)](#subscribewithprevious-type-alias) + - [SubscribeGetter (type alias)](#subscribegetter-type-alias) - [RxResult (interface)](#rxresult-interface) + - [RxResultFn (interface)](#rxresultfn-interface) - [RxRuntime (interface)](#rxruntime-interface) - [Writeable (interface)](#writeable-interface) - [type ids](#type-ids) @@ -117,6 +120,32 @@ export declare const effect: { Added in v1.0.0 +## effectFn + +**Signature** + +```ts +export declare const effectFn: { + (fn: (...args: Args) => Effect.Effect): RxResultFn + ( + fn: (...args: Args) => Effect.Effect, + runtime: RxRuntime + ): RxResultFn +} +``` + +Added in v1.0.0 + +## fn + +**Signature** + +```ts +export declare const fn: (initialValue: A, fn: (...args: Args) => A) => Writeable +``` + +Added in v1.0.0 + ## readable **Signature** @@ -156,6 +185,26 @@ export declare const scoped: { Added in v1.0.0 +## scopedFn + +**Signature** + +```ts +export declare const scopedFn: { + (fn: (...args: Args) => Effect.Effect): RxResultFn< + E, + A, + Args + > + ( + fn: (...args: Args) => Effect.Effect, + runtime: RxRuntime + ): RxResultFn +} +``` + +Added in v1.0.0 + ## state **Signature** @@ -303,16 +352,6 @@ export type Mount = (rx: Rx) => () => void Added in v1.0.0 -### Queue (type alias) - -**Signature** - -```ts -export type Queue = (rx: Rx) => Effect.Effect> -``` - -Added in v1.0.0 - ### Refresh (type alias) **Signature** @@ -349,18 +388,12 @@ export type Subscribe = ( Added in v1.0.0 -### SubscribeWithPrevious (type alias) +### SubscribeGetter (type alias) **Signature** ```ts -export type SubscribeWithPrevious = ( - rx: Rx, - f: (prev: Option.Option, value: A) => void, - options?: { - readonly immediate?: boolean - } -) => () => void +export type SubscribeGetter = (rx: Rx, f: () => void) => readonly [get: () => A, unmount: () => void] ``` Added in v1.0.0 @@ -375,6 +408,16 @@ export interface RxResult extends Rx> {} Added in v1.0.0 +## RxResultFn (interface) + +**Signature** + +```ts +export interface RxResultFn> extends Writeable, Args> {} +``` + +Added in v1.0.0 + ## RxRuntime (interface) **Signature** diff --git a/packages/rx-react/src/index.ts b/packages/rx-react/src/index.ts index c52d8d3..389864f 100644 --- a/packages/rx-react/src/index.ts +++ b/packages/rx-react/src/index.ts @@ -54,36 +54,35 @@ export const useRxValue = (rx: Rx.Rx): A => { * @since 1.0.0 * @category hooks */ -export const useSetRx = (rx: Rx.Writeable): (_: W) => void => { +export const useSetRx = (rx: Rx.Writeable): (_: W | ((_: R) => W)) => void => { const registry = React.useContext(RegistryContext) - return React.useCallback((value) => registry.set(rx, value), [registry, rx]) + return React.useCallback((value) => { + if (typeof value === "function") { + registry.set(rx, (value as any)(registry.get(rx))) + return + } else { + registry.set(rx, value) + } + }, [registry, rx]) } /** * @since 1.0.0 * @category hooks */ -export const useUpdateRx = (rx: Rx.Writeable): (f: (_: R) => W) => void => { +export const useRefreshRx = (rx: Rx.Rx & Rx.Refreshable): () => void => { const registry = React.useContext(RegistryContext) - return React.useCallback((f) => registry.set(rx, f(registry.get(rx))), [registry, rx]) + return React.useCallback(() => { + registry.refresh(rx) + }, [registry, rx]) } /** * @since 1.0.0 * @category hooks */ -export const useRx = (rx: Rx.Writeable): readonly [R, (_: W) => void] => +export const useRx = (rx: Rx.Writeable): readonly [value: R, setOrUpdate: (_: W | ((_: R) => W)) => void] => [ useRxValue(rx), useSetRx(rx) ] as const - -/** - * @since 1.0.0 - * @category hooks - */ -export const useRxUpdate = (rx: Rx.Writeable): readonly [R, (f: (_: R) => W) => void] => - [ - useRxValue(rx), - useUpdateRx(rx) - ] as const diff --git a/packages/rx/src/Rx.ts b/packages/rx/src/Rx.ts index 6d92b91..a16b517 100644 --- a/packages/rx/src/Rx.ts +++ b/packages/rx/src/Rx.ts @@ -323,11 +323,11 @@ export const effect: { ): RxResult } = ( effect: Effect.Effect, - runtime?: RxRuntime> + runtime?: RxRuntime ) => readable>(function(ctx) { if (runtime !== undefined) { - return makeEffectRuntime(ctx, effect, runtime as any) + return makeEffectRuntime(ctx, effect, runtime) } return makeEffect(ctx, effect as Effect.Effect) }) @@ -363,6 +363,90 @@ export const scoped: { return makeEffect(ctx, scopedEffect) }) +/** + * @since 1.0.0 + * @category models + */ +export interface RxResultFn> extends Writeable, Args> {} + +/** + * @since 1.0.0 + * @category constructors + */ +export const fn = >( + initialValue: A, + fn: (...args: Args) => A +) => + writable(function(_ctx) { + return initialValue + }, function(_get, _set, setSelf, args) { + setSelf(fn(...args)) + }) + +/** + * @since 1.0.0 + * @category constructors + */ +export const effectFn: { + , E, A>(fn: (...args: Args) => Effect.Effect): RxResultFn + , RR, R extends (RR | RxContext), E, A, RE>( + fn: (...args: Args) => Effect.Effect, + runtime: RxRuntime + ): RxResultFn +} = , R, E, A, RE>( + f: (...args: Args) => Effect.Effect, + runtime?: RxRuntime +) => { + const effectRx = state | undefined>(undefined) + return writable, Args>(function(ctx) { + const effect = ctx.get(effectRx) + if (effect === undefined) { + return Result.initial() + } + return runtime ? makeEffectRuntime(ctx, effect, runtime) : makeEffect(ctx, effect as Effect.Effect) + }, function(_get, set, _setSelf, args) { + set(effectRx, f(...args)) + }) +} + +/** + * @since 1.0.0 + * @category constructors + */ +export const scopedFn: { + , E, A>( + fn: (...args: Args) => Effect.Effect + ): RxResultFn + , RR, R extends (RR | RxContext | Scope.Scope), E, A, RE>( + fn: (...args: Args) => Effect.Effect, + runtime: RxRuntime + ): RxResultFn +} = , R, E, A, RE>( + f: (...args: Args) => Effect.Effect, + runtime?: RxRuntime +) => { + const effectRx = state | undefined>(undefined) + return writable, Args>(function(ctx) { + const effect = ctx.get(effectRx) + if (effect === undefined) { + return Result.initial() + } + const scope = Effect.runSync(Scope.make()) + ctx.addFinalizer(() => Effect.runFork(Scope.close(scope, Exit.unit))) + + const scopedEffect = Effect.provideService( + effect as Effect.Effect, + Scope.Scope, + scope + ) + return runtime + ? makeEffectRuntime(ctx, scopedEffect, runtime) + : makeEffect(ctx, scopedEffect as Effect.Effect) + }, function(_get, set, _setSelf, args) { + set(effectRx, f(...args)) + }) +} + /** * @since 1.0.0 * @category models diff --git a/packages/rx/test/Rx.test.ts b/packages/rx/test/Rx.test.ts index cc1b125..ee82530 100644 --- a/packages/rx/test/Rx.test.ts +++ b/packages/rx/test/Rx.test.ts @@ -91,6 +91,47 @@ describe("Rx", () => { assert(Result.isSuccess(result)) expect(result.value).toEqual(1) }) + + it("effectFn", async () => { + const count = Rx.effectFn((n: number) => Effect.succeed(n + 1)) + const r = Registry.make() + let result = r.get(count) + assert(Result.isInitial(result)) + r.set(count, [1]) + result = r.get(count) + assert(Result.isSuccess(result)) + expect(result.value).toEqual(2) + }) + + it("scopedFn", async () => { + let finalized = 0 + const count = Rx.scopedFn((n: number) => + Effect.succeed(n + 1).pipe( + Effect.zipLeft( + Effect.addFinalizer(() => + Effect.sync(() => { + finalized++ + }) + ) + ) + ) + ).pipe(Rx.keepAlive) + const r = Registry.make() + let result = r.get(count) + assert(Result.isInitial(result)) + + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(finalized).toEqual(0) + + r.set(count, [1]) + result = r.get(count) + assert(Result.isSuccess(result)) + expect(result.value).toEqual(2) + + r.set(count, [2]) + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(finalized).toEqual(1) + }) }) interface Counter {