From 0fd3fe49a9d51c760c860a1a1c76bca1637272b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Mon, 21 Apr 2025 02:08:14 +0200 Subject: [PATCH] 0.1.8 (#11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Julien Valverdé Reviewed-on: https://gitea:3000/Thilawyn/reffuse/pulls/11 --- packages/example/src/routes/tests.tsx | 14 +++- packages/extension-lazyref/package.json | 4 +- packages/extension-lazyref/src/index.ts | 3 +- packages/reffuse/package.json | 6 +- packages/reffuse/src/Reffuse.ts | 2 +- packages/reffuse/src/ReffuseNamespace.ts | 14 +++- packages/reffuse/src/index.ts | 1 - .../reffuse/src/{ => types}/SetStateAction.ts | 0 .../reffuse/src/types/SubscriptionSubRef.ts | 77 +++++++++++++++++++ packages/reffuse/src/types/index.ts | 2 + packages/reffuse/src/{types.ts => utils.ts} | 0 11 files changed, 115 insertions(+), 8 deletions(-) rename packages/reffuse/src/{ => types}/SetStateAction.ts (100%) create mode 100644 packages/reffuse/src/types/SubscriptionSubRef.ts create mode 100644 packages/reffuse/src/types/index.ts rename packages/reffuse/src/{types.ts => utils.ts} (100%) diff --git a/packages/example/src/routes/tests.tsx b/packages/example/src/routes/tests.tsx index 64bac4d..def5d5c 100644 --- a/packages/example/src/routes/tests.tsx +++ b/packages/example/src/routes/tests.tsx @@ -10,6 +10,13 @@ export const Route = createFileRoute("/tests")({ }) function RouteComponent() { + const deepRef = R.useRef({ value: "poulet" }) + const deepValueRef = R.useSubRefFromGetSet( + deepRef, + b => b.value, + (b, a) => ({ ...b, value: a }), + ) + // const value = R.useMemoScoped(Effect.addFinalizer(() => Console.log("cleanup")).pipe( // Effect.andThen(makeUuid4), // Effect.provide(GetRandomValues.CryptoRandom), @@ -32,7 +39,8 @@ function RouteComponent() { const generateUuid = R.useCallbackSync(() => makeUuid4.pipe( Effect.provide(GetRandomValues.CryptoRandom), - Effect.flatMap(v => Ref.set(uuidRef, v)), + Effect.tap(v => Ref.set(uuidRef, v)), + Effect.tap(v => Ref.set(deepValueRef, v)), ), []) @@ -42,6 +50,10 @@ function RouteComponent() { {(uuid, anotherRef) => {uuid} / {anotherRef}} + + {(deep, deepValue) => {JSON.stringify(deep)} / {deepValue}} + + diff --git a/packages/extension-lazyref/package.json b/packages/extension-lazyref/package.json index f126cdd..14e6ab6 100644 --- a/packages/extension-lazyref/package.json +++ b/packages/extension-lazyref/package.json @@ -1,6 +1,6 @@ { "name": "@reffuse/extension-lazyref", - "version": "0.1.3", + "version": "0.1.4", "type": "module", "files": [ "./README.md", @@ -37,6 +37,6 @@ "@types/react": "^19.0.0", "effect": "^3.13.0", "react": "^19.0.0", - "reffuse": "^0.1.7" + "reffuse": "^0.1.8" } } diff --git a/packages/extension-lazyref/src/index.ts b/packages/extension-lazyref/src/index.ts index 7a51a86..70c1f39 100644 --- a/packages/extension-lazyref/src/index.ts +++ b/packages/extension-lazyref/src/index.ts @@ -1,7 +1,8 @@ import * as LazyRef from "@typed/lazy-ref" import { Effect, pipe, Stream } from "effect" import * as React from "react" -import { ReffuseExtension, type ReffuseNamespace, SetStateAction } from "reffuse" +import { ReffuseExtension, type ReffuseNamespace } from "reffuse" +import { SetStateAction } from "reffuse/types" export const LazyRefExtension = ReffuseExtension.make(() => ({ diff --git a/packages/reffuse/package.json b/packages/reffuse/package.json index 5e189c2..907e111 100644 --- a/packages/reffuse/package.json +++ b/packages/reffuse/package.json @@ -1,6 +1,6 @@ { "name": "reffuse", - "version": "0.1.7", + "version": "0.1.8", "type": "module", "files": [ "./README.md", @@ -16,6 +16,10 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" }, + "./types": { + "types": "./dist/types/index.d.ts", + "default": "./dist/types/index.js" + }, "./*": { "types": "./dist/*.d.ts", "default": "./dist/*.js" diff --git a/packages/reffuse/src/Reffuse.ts b/packages/reffuse/src/Reffuse.ts index 547342a..ad95a80 100644 --- a/packages/reffuse/src/Reffuse.ts +++ b/packages/reffuse/src/Reffuse.ts @@ -1,7 +1,7 @@ import type * as ReffuseContext from "./ReffuseContext.js" import type * as ReffuseExtension from "./ReffuseExtension.js" import * as ReffuseNamespace from "./ReffuseNamespace.js" -import type { Merge, StaticType } from "./types.js" +import type { Merge, StaticType } from "./utils.js" export class Reffuse extends ReffuseNamespace.makeClass() {} diff --git a/packages/reffuse/src/ReffuseNamespace.ts b/packages/reffuse/src/ReffuseNamespace.ts index a6d56e3..1ad1d10 100644 --- a/packages/reffuse/src/ReffuseNamespace.ts +++ b/packages/reffuse/src/ReffuseNamespace.ts @@ -2,7 +2,7 @@ import { type Context, Effect, ExecutionStrategy, Exit, type Fiber, type Layer, import * as React from "react" import * as ReffuseContext from "./ReffuseContext.js" import * as ReffuseRuntime from "./ReffuseRuntime.js" -import * as SetStateAction from "./SetStateAction.js" +import { SetStateAction, SubscriptionSubRef } from "./types/index.js" export interface RenderOptions { @@ -384,6 +384,18 @@ export abstract class ReffuseNamespace { ) } + useSubRefFromGetSet( + this: ReffuseNamespace, + parent: SubscriptionRef.SubscriptionRef, + getter: (parentValue: B) => A, + setter: (parentValue: B, value: A) => B, + ): SubscriptionSubRef.SubscriptionSubRef { + return React.useMemo( + () => SubscriptionSubRef.makeFromGetSet(parent, getter, setter), + [parent], + ) + } + useSubscribeRefs< const Refs extends readonly SubscriptionRef.SubscriptionRef[], R, diff --git a/packages/reffuse/src/index.ts b/packages/reffuse/src/index.ts index 01fb88f..c99d6b2 100644 --- a/packages/reffuse/src/index.ts +++ b/packages/reffuse/src/index.ts @@ -3,4 +3,3 @@ export * as ReffuseContext from "./ReffuseContext.js" export * as ReffuseExtension from "./ReffuseExtension.js" export * as ReffuseNamespace from "./ReffuseNamespace.js" export * as ReffuseRuntime from "./ReffuseRuntime.js" -export * as SetStateAction from "./SetStateAction.js" diff --git a/packages/reffuse/src/SetStateAction.ts b/packages/reffuse/src/types/SetStateAction.ts similarity index 100% rename from packages/reffuse/src/SetStateAction.ts rename to packages/reffuse/src/types/SetStateAction.ts diff --git a/packages/reffuse/src/types/SubscriptionSubRef.ts b/packages/reffuse/src/types/SubscriptionSubRef.ts new file mode 100644 index 0000000..cb7622e --- /dev/null +++ b/packages/reffuse/src/types/SubscriptionSubRef.ts @@ -0,0 +1,77 @@ +import { Effect, Effectable, Readable, Ref, Stream, Subscribable, SubscriptionRef, SynchronizedRef, type Types } from "effect" + + +export const SubscriptionSubRefTypeId: unique symbol = Symbol.for("reffuse/types/SubscriptionSubRef") + +export interface SubscriptionSubRef extends SubscriptionSubRef.Variance, SubscriptionRef.SubscriptionRef { + readonly parent: SubscriptionRef.SubscriptionRef +} + +export declare namespace SubscriptionSubRef { + export interface Variance { + readonly [SubscriptionSubRefTypeId]: { + readonly _A: Types.Invariant + readonly _B: Types.Invariant + } + } +} + + +const refVariance = { _A: (_: any) => _ } +const synchronizedRefVariance = { _A: (_: any) => _ } +const subscriptionRefVariance = { _A: (_: any) => _ } +const subscriptionSubRefVariance = { _A: (_: any) => _, _B: (_: any) => _ } + +class SubscriptionSubRefImpl extends Effectable.Class implements SubscriptionSubRef { + readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId + readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId + readonly [Ref.RefTypeId] = refVariance + readonly [SynchronizedRef.SynchronizedRefTypeId] = synchronizedRefVariance + readonly [SubscriptionRef.SubscriptionRefTypeId] = subscriptionRefVariance + readonly [SubscriptionSubRefTypeId] = subscriptionSubRefVariance + + readonly get: Effect.Effect + + constructor( + readonly parent: SubscriptionRef.SubscriptionRef, + readonly getter: (parentValue: B) => A, + readonly setter: (parentValue: B, value: A) => B, + ) { + super() + this.get = Ref.get(this.parent).pipe(Effect.map(this.getter)) + } + + commit() { + return this.get + } + + get changes(): Stream.Stream { + return this.get.pipe( + Effect.map(a => this.parent.changes.pipe( + Stream.map(this.getter), + s => Stream.concat(Stream.make(a), s), + )), + Stream.unwrap, + ) + } + + modify(f: (a: A) => readonly [C, A]): Effect.Effect { + return this.modifyEffect(a => Effect.succeed(f(a))) + } + + modifyEffect(f: (a: A) => Effect.Effect): Effect.Effect { + return Effect.Do.pipe( + Effect.bind("b", () => Ref.get(this.parent)), + Effect.bind("ca", ({ b }) => f(this.getter(b))), + Effect.tap(({ b, ca: [, a] }) => Ref.set(this.parent, this.setter(b, a))), + Effect.map(({ ca: [c] }) => c), + ) + } +} + + +export const makeFromGetSet = ( + parent: SubscriptionRef.SubscriptionRef, + getter: (parentValue: B) => A, + setter: (parentValue: B, value: A) => B, +): SubscriptionSubRef => new SubscriptionSubRefImpl(parent, getter, setter) diff --git a/packages/reffuse/src/types/index.ts b/packages/reffuse/src/types/index.ts new file mode 100644 index 0000000..ec93c5f --- /dev/null +++ b/packages/reffuse/src/types/index.ts @@ -0,0 +1,2 @@ +export * as SetStateAction from "./SetStateAction.js" +export * as SubscriptionSubRef from "./SubscriptionSubRef.js" diff --git a/packages/reffuse/src/types.ts b/packages/reffuse/src/utils.ts similarity index 100% rename from packages/reffuse/src/types.ts rename to packages/reffuse/src/utils.ts