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