From 051226ebd46d70758069aecfd2c3f14ef26ec80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sat, 26 Jul 2025 01:35:02 +0200 Subject: [PATCH] SubscriptionSubRef refactoring --- .../effect-fc/src/types/SubscriptionSubRef.ts | 55 +++++++++++-------- packages/example/src/routes/dev/memo.tsx | 6 +- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/packages/effect-fc/src/types/SubscriptionSubRef.ts b/packages/effect-fc/src/types/SubscriptionSubRef.ts index fead780..88baee8 100644 --- a/packages/effect-fc/src/types/SubscriptionSubRef.ts +++ b/packages/effect-fc/src/types/SubscriptionSubRef.ts @@ -5,7 +5,8 @@ import * as PropertyPath from "./PropertyPath.js" export const SubscriptionSubRefTypeId: unique symbol = Symbol.for("effect-fc/types/SubscriptionSubRef") export type SubscriptionSubRefTypeId = typeof SubscriptionSubRefTypeId -export interface SubscriptionSubRef extends SubscriptionSubRef.Variance, SubscriptionRef.SubscriptionRef { +export interface SubscriptionSubRef> +extends SubscriptionSubRef.Variance, SubscriptionRef.SubscriptionRef { readonly parent: SubscriptionRef.SubscriptionRef readonly [Unify.typeSymbol]?: unknown @@ -36,7 +37,8 @@ const synchronizedRefVariance = { _A: (_: any) => _ } const subscriptionRefVariance = { _A: (_: any) => _ } const subscriptionSubRefVariance = { _A: (_: any) => _, _B: (_: any) => _ } -class SubscriptionSubRefImpl extends Effectable.Class implements SubscriptionSubRef { +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 @@ -47,9 +49,9 @@ class SubscriptionSubRefImpl extends Effectable.Class imp readonly get: Effect.Effect constructor( - readonly parent: SubscriptionRef.SubscriptionRef, - readonly getter: (parentValue: B) => A, - readonly setter: (parentValue: B, value: A) => B, + readonly parent: B, + readonly getter: (parentValue: Effect.Effect.Success) => A, + readonly setter: (parentValue: Effect.Effect.Success, value: A) => Effect.Effect.Success, ) { super() this.get = Effect.map(this.parent, this.getter) @@ -60,12 +62,11 @@ class SubscriptionSubRefImpl extends Effectable.Class imp } 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, + return Stream.unwrap( + Effect.map(this.get, a => Stream.concat( + Stream.make(a), + Stream.map(this.parent.changes, this.getter), + )) ) } @@ -75,7 +76,7 @@ class SubscriptionSubRefImpl extends Effectable.Class imp modifyEffect(f: (a: A) => Effect.Effect): Effect.Effect { return Effect.Do.pipe( - Effect.bind("b", () => this.parent), + Effect.bind("b", (): Effect.Effect> => 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), @@ -84,28 +85,34 @@ class SubscriptionSubRefImpl extends Effectable.Class imp } -export const makeFromGetSet = ( - parent: SubscriptionRef.SubscriptionRef, +export const makeFromGetSet = >( + parent: B, options: { - readonly get: (parentValue: B) => A - readonly set: (parentValue: B, value: A) => B + readonly get: (parentValue: Effect.Effect.Success) => A + readonly set: (parentValue: Effect.Effect.Success, value: A) => Effect.Effect.Success }, ): SubscriptionSubRef => new SubscriptionSubRefImpl(parent, options.get, options.set) -export const makeFromPath = >( - parent: SubscriptionRef.SubscriptionRef, +export const makeFromPath = < + B extends SubscriptionRef.SubscriptionRef, + const P extends PropertyPath.Paths>, +>( + parent: B, path: P, -): SubscriptionSubRef, B> => new SubscriptionSubRefImpl( +): SubscriptionSubRef, P>, B> => new SubscriptionSubRefImpl( parent, parentValue => Option.getOrThrow(PropertyPath.get(parentValue, path)), (parentValue, value) => Option.getOrThrow(PropertyPath.immutableSet(parentValue, path, value)), ) -export const makeFromChunkRef = ( - parent: SubscriptionRef.SubscriptionRef>, +export const makeFromChunkRef = | Chunk.NonEmptyChunk>>( + parent: B, index: number, -): SubscriptionSubRef> => new SubscriptionSubRefImpl( - parent, +): SubscriptionSubRef< + Effect.Effect.Success extends Chunk.Chunk ? A : never, + B +> => new SubscriptionSubRefImpl( + parent as SubscriptionRef.SubscriptionRef>, parentValue => Chunk.unsafeGet(parentValue, index), (parentValue, value) => Chunk.replace(parentValue, index, value), -) +) as any diff --git a/packages/example/src/routes/dev/memo.tsx b/packages/example/src/routes/dev/memo.tsx index 42f2f41..4acd3cb 100644 --- a/packages/example/src/routes/dev/memo.tsx +++ b/packages/example/src/routes/dev/memo.tsx @@ -2,13 +2,17 @@ import { runtime } from "@/runtime" import { Flex, Text, TextField } from "@radix-ui/themes" import { createFileRoute } from "@tanstack/react-router" import { GetRandomValues, makeUuid4 } from "@typed/id" -import { Effect } from "effect" +import { Chunk, Effect, SubscriptionRef } from "effect" import { Component, Memoized } from "effect-fc" +import { SubscriptionSubRef } from "effect-fc/types" import * as React from "react" const RouteComponent = Component.make(function* RouteComponent() { const [value, setValue] = React.useState("") + const myRef = yield* SubscriptionRef.make(Chunk.make({ name: "person 1" } as const)) + // const myRef = yield* SubscriptionRef.make(Chunk.empty<{ readonly name: "person 1" }>()) + const mySubRef = SubscriptionSubRef.makeFromChunkRef(myRef, 0) return (