SubscriptionSubRef refactoring
All checks were successful
Lint / lint (push) Successful in 12s

This commit is contained in:
Julien Valverdé
2025-07-26 01:35:02 +02:00
parent 35463d5607
commit 051226ebd4
2 changed files with 36 additions and 25 deletions

View File

@@ -5,7 +5,8 @@ import * as PropertyPath from "./PropertyPath.js"
export const SubscriptionSubRefTypeId: unique symbol = Symbol.for("effect-fc/types/SubscriptionSubRef") export const SubscriptionSubRefTypeId: unique symbol = Symbol.for("effect-fc/types/SubscriptionSubRef")
export type SubscriptionSubRefTypeId = typeof SubscriptionSubRefTypeId export type SubscriptionSubRefTypeId = typeof SubscriptionSubRefTypeId
export interface SubscriptionSubRef<in out A, in out B> extends SubscriptionSubRef.Variance<A, B>, SubscriptionRef.SubscriptionRef<A> { export interface SubscriptionSubRef<in out A, in out B extends SubscriptionRef.SubscriptionRef<any>>
extends SubscriptionSubRef.Variance<A, B>, SubscriptionRef.SubscriptionRef<A> {
readonly parent: SubscriptionRef.SubscriptionRef<B> readonly parent: SubscriptionRef.SubscriptionRef<B>
readonly [Unify.typeSymbol]?: unknown readonly [Unify.typeSymbol]?: unknown
@@ -36,7 +37,8 @@ const synchronizedRefVariance = { _A: (_: any) => _ }
const subscriptionRefVariance = { _A: (_: any) => _ } const subscriptionRefVariance = { _A: (_: any) => _ }
const subscriptionSubRefVariance = { _A: (_: any) => _, _B: (_: any) => _ } const subscriptionSubRefVariance = { _A: (_: any) => _, _B: (_: any) => _ }
class SubscriptionSubRefImpl<in out A, in out B> extends Effectable.Class<A> implements SubscriptionSubRef<A, B> { class SubscriptionSubRefImpl<in out A, in out B extends SubscriptionRef.SubscriptionRef<any>>
extends Effectable.Class<A> implements SubscriptionSubRef<A, B> {
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
readonly [Ref.RefTypeId] = refVariance readonly [Ref.RefTypeId] = refVariance
@@ -47,9 +49,9 @@ class SubscriptionSubRefImpl<in out A, in out B> extends Effectable.Class<A> imp
readonly get: Effect.Effect<A> readonly get: Effect.Effect<A>
constructor( constructor(
readonly parent: SubscriptionRef.SubscriptionRef<B>, readonly parent: B,
readonly getter: (parentValue: B) => A, readonly getter: (parentValue: Effect.Effect.Success<B>) => A,
readonly setter: (parentValue: B, value: A) => B, readonly setter: (parentValue: Effect.Effect.Success<B>, value: A) => Effect.Effect.Success<B>,
) { ) {
super() super()
this.get = Effect.map(this.parent, this.getter) this.get = Effect.map(this.parent, this.getter)
@@ -60,12 +62,11 @@ class SubscriptionSubRefImpl<in out A, in out B> extends Effectable.Class<A> imp
} }
get changes(): Stream.Stream<A> { get changes(): Stream.Stream<A> {
return this.get.pipe( return Stream.unwrap(
Effect.map(a => this.parent.changes.pipe( Effect.map(this.get, a => Stream.concat(
Stream.map(this.getter), Stream.make(a),
s => Stream.concat(Stream.make(a), s), Stream.map(this.parent.changes, this.getter),
)), ))
Stream.unwrap,
) )
} }
@@ -75,7 +76,7 @@ class SubscriptionSubRefImpl<in out A, in out B> extends Effectable.Class<A> imp
modifyEffect<C, E, R>(f: (a: A) => Effect.Effect<readonly [C, A], E, R>): Effect.Effect<C, E, R> { modifyEffect<C, E, R>(f: (a: A) => Effect.Effect<readonly [C, A], E, R>): Effect.Effect<C, E, R> {
return Effect.Do.pipe( return Effect.Do.pipe(
Effect.bind("b", () => this.parent), Effect.bind("b", (): Effect.Effect<Effect.Effect.Success<B>> => this.parent),
Effect.bind("ca", ({ b }) => f(this.getter(b))), Effect.bind("ca", ({ b }) => f(this.getter(b))),
Effect.tap(({ b, ca: [, a] }) => Ref.set(this.parent, this.setter(b, a))), Effect.tap(({ b, ca: [, a] }) => Ref.set(this.parent, this.setter(b, a))),
Effect.map(({ ca: [c] }) => c), Effect.map(({ ca: [c] }) => c),
@@ -84,28 +85,34 @@ class SubscriptionSubRefImpl<in out A, in out B> extends Effectable.Class<A> imp
} }
export const makeFromGetSet = <A, B>( export const makeFromGetSet = <A, B extends SubscriptionRef.SubscriptionRef<any>>(
parent: SubscriptionRef.SubscriptionRef<B>, parent: B,
options: { options: {
readonly get: (parentValue: B) => A readonly get: (parentValue: Effect.Effect.Success<B>) => A
readonly set: (parentValue: B, value: A) => B readonly set: (parentValue: Effect.Effect.Success<B>, value: A) => Effect.Effect.Success<B>
}, },
): SubscriptionSubRef<A, B> => new SubscriptionSubRefImpl(parent, options.get, options.set) ): SubscriptionSubRef<A, B> => new SubscriptionSubRefImpl(parent, options.get, options.set)
export const makeFromPath = <B, const P extends PropertyPath.Paths<B>>( export const makeFromPath = <
parent: SubscriptionRef.SubscriptionRef<B>, B extends SubscriptionRef.SubscriptionRef<any>,
const P extends PropertyPath.Paths<Effect.Effect.Success<B>>,
>(
parent: B,
path: P, path: P,
): SubscriptionSubRef<PropertyPath.ValueFromPath<B, P>, B> => new SubscriptionSubRefImpl( ): SubscriptionSubRef<PropertyPath.ValueFromPath<Effect.Effect.Success<B>, P>, B> => new SubscriptionSubRefImpl(
parent, parent,
parentValue => Option.getOrThrow(PropertyPath.get(parentValue, path)), parentValue => Option.getOrThrow(PropertyPath.get(parentValue, path)),
(parentValue, value) => Option.getOrThrow(PropertyPath.immutableSet(parentValue, path, value)), (parentValue, value) => Option.getOrThrow(PropertyPath.immutableSet(parentValue, path, value)),
) )
export const makeFromChunkRef = <A>( export const makeFromChunkRef = <A, B extends SubscriptionRef.SubscriptionRef<Chunk.Chunk<any> | Chunk.NonEmptyChunk<any>>>(
parent: SubscriptionRef.SubscriptionRef<Chunk.Chunk<A>>, parent: B,
index: number, index: number,
): SubscriptionSubRef<A, Chunk.Chunk<A>> => new SubscriptionSubRefImpl( ): SubscriptionSubRef<
parent, Effect.Effect.Success<B> extends Chunk.Chunk<infer A> ? A : never,
B
> => new SubscriptionSubRefImpl(
parent as SubscriptionRef.SubscriptionRef<Chunk.Chunk<any>>,
parentValue => Chunk.unsafeGet(parentValue, index), parentValue => Chunk.unsafeGet(parentValue, index),
(parentValue, value) => Chunk.replace(parentValue, index, value), (parentValue, value) => Chunk.replace(parentValue, index, value),
) ) as any

View File

@@ -2,13 +2,17 @@ import { runtime } from "@/runtime"
import { Flex, Text, TextField } from "@radix-ui/themes" import { Flex, Text, TextField } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { GetRandomValues, makeUuid4 } from "@typed/id" import { GetRandomValues, makeUuid4 } from "@typed/id"
import { Effect } from "effect" import { Chunk, Effect, SubscriptionRef } from "effect"
import { Component, Memoized } from "effect-fc" import { Component, Memoized } from "effect-fc"
import { SubscriptionSubRef } from "effect-fc/types"
import * as React from "react" import * as React from "react"
const RouteComponent = Component.make(function* RouteComponent() { const RouteComponent = Component.make(function* RouteComponent() {
const [value, setValue] = React.useState("") 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 ( return (
<Flex direction="column" gap="2"> <Flex direction="column" gap="2">