16 Commits

Author SHA1 Message Date
Julien Valverdé
ea374d7e0f Fix
All checks were successful
Lint / lint (push) Successful in 13s
2025-04-25 08:32:42 +02:00
Julien Valverdé
148c98acbd Fix
All checks were successful
Lint / lint (push) Successful in 13s
2025-04-25 08:30:17 +02:00
Julien Valverdé
39d2176c61 Working subref from path
All checks were successful
Lint / lint (push) Successful in 13s
2025-04-25 08:21:59 +02:00
Julien Valverdé
107ff1e794 SubscriptionSubRef.makeFromPath
All checks were successful
Lint / lint (push) Successful in 14s
2025-04-25 08:12:34 +02:00
Julien Valverdé
a70ef27f75 PropertyPath done
All checks were successful
Lint / lint (push) Successful in 14s
2025-04-25 07:56:56 +02:00
Julien Valverdé
04b2fad038 PropertyPath
All checks were successful
Lint / lint (push) Successful in 29s
2025-04-25 07:40:21 +02:00
Julien Valverdé
691b28427d SearchPaths work
All checks were successful
Lint / lint (push) Successful in 14s
2025-04-24 00:52:18 +02:00
Julien Valverdé
1de976aaa8 Fix
All checks were successful
Lint / lint (push) Successful in 14s
2025-04-24 00:20:30 +02:00
Julien Valverdé
df851cf9ee SearchPaths work
All checks were successful
Lint / lint (push) Successful in 13s
2025-04-23 07:06:32 +02:00
Julien Valverdé
459f548c10 Fix
All checks were successful
Lint / lint (push) Successful in 14s
2025-04-23 06:50:17 +02:00
Julien Valverdé
6156baec4d SearchPaths work
All checks were successful
Lint / lint (push) Successful in 14s
2025-04-23 06:47:11 +02:00
Julien Valverdé
1163b83929 SearchPaths work
Some checks failed
Lint / lint (push) Failing after 13s
2025-04-23 05:53:19 +02:00
Julien Valverdé
8917f84952 SearchPaths work
All checks were successful
Lint / lint (push) Successful in 14s
2025-04-22 22:59:50 +02:00
Julien Valverdé
58752253b3 SearchPaths work
All checks were successful
Lint / lint (push) Successful in 14s
2025-04-22 22:36:17 +02:00
Julien Valverdé
ba362baf04 SearchPaths work
All checks were successful
Lint / lint (push) Successful in 15s
2025-04-22 21:55:59 +02:00
Julien Valverdé
33cf4fbcbd Tests
All checks were successful
Lint / lint (push) Successful in 14s
2025-04-21 05:15:55 +02:00
5 changed files with 118 additions and 7 deletions

View File

@@ -11,11 +11,7 @@ export const Route = createFileRoute("/tests")({
function RouteComponent() { function RouteComponent() {
const deepRef = R.useRef({ value: "poulet" }) const deepRef = R.useRef({ value: "poulet" })
const deepValueRef = R.useSubRefFromGetSet( const deepValueRef = R.useSubRefFromPath(deepRef, ["value"])
deepRef,
b => b.value,
(b, a) => ({ ...b, value: a }),
)
// const value = R.useMemoScoped(Effect.addFinalizer(() => Console.log("cleanup")).pipe( // const value = R.useMemoScoped(Effect.addFinalizer(() => Console.log("cleanup")).pipe(
// Effect.andThen(makeUuid4), // Effect.andThen(makeUuid4),

View File

@@ -2,7 +2,7 @@ import { type Context, Effect, ExecutionStrategy, Exit, type Fiber, type Layer,
import * as React from "react" import * as React from "react"
import * as ReffuseContext from "./ReffuseContext.js" import * as ReffuseContext from "./ReffuseContext.js"
import * as ReffuseRuntime from "./ReffuseRuntime.js" import * as ReffuseRuntime from "./ReffuseRuntime.js"
import { SetStateAction, SubscriptionSubRef } from "./types/index.js" import { type PropertyPath, SetStateAction, SubscriptionSubRef } from "./types/index.js"
export interface RenderOptions { export interface RenderOptions {
@@ -396,6 +396,17 @@ export abstract class ReffuseNamespace<R> {
) )
} }
useSubRefFromPath<B, const P extends PropertyPath.Paths<B>, R>(
this: ReffuseNamespace<R>,
parent: SubscriptionRef.SubscriptionRef<B>,
path: P,
): SubscriptionSubRef.SubscriptionSubRef<PropertyPath.ValueFromPath<B, P>, B> {
return React.useMemo(
() => SubscriptionSubRef.makeFromPath(parent, path),
[parent],
)
}
useSubscribeRefs< useSubscribeRefs<
const Refs extends readonly SubscriptionRef.SubscriptionRef<any>[], const Refs extends readonly SubscriptionRef.SubscriptionRef<any>[],
R, R,

View File

@@ -0,0 +1,93 @@
import { Array, Option, Predicate } from "effect"
export type Paths<T> = [] | (
T extends readonly any[] ? ArrayPaths<T> :
T extends object ? ObjectPaths<T> :
never
)
export type ArrayPaths<T extends readonly any[]> = {
[K in keyof T as K extends number ? K : never]:
| [K]
| [K, ...Paths<T[K]>]
} extends infer O
? O[keyof O]
: never
export type ObjectPaths<T extends object> = {
[K in keyof T as K extends string | number | symbol ? K : never]:
| [K]
| [K, ...Paths<T[K]>]
} extends infer O
? O[keyof O]
: never
export type ValueFromPath<T, P extends any[]> = P extends [infer Head, ...infer Tail]
? Head extends keyof T
? ValueFromPath<T[Head], Tail>
: T extends readonly any[]
? Head extends number
? ValueFromPath<T[number], Tail>
: never
: never
: T
export type AnyKey = string | number | symbol
export type AnyPath = readonly AnyKey[]
export const unsafeGet = <T, const P extends Paths<T>>(
parent: T,
path: P,
): ValueFromPath<T, P> => (
path.reduce((acc: any, key: any) => acc?.[key], parent)
)
export const get = <T, const P extends Paths<T>>(
parent: T,
path: P,
): Option.Option<ValueFromPath<T, P>> => path.reduce(
(acc: Option.Option<any>, key: any): Option.Option<any> => Option.isSome(acc)
? Predicate.hasProperty(acc.value, key)
? Option.some(acc.value[key])
: Option.none()
: acc,
Option.some(parent),
)
export const immutableSet = <T, const P extends Paths<T>>(
parent: T,
path: P,
value: ValueFromPath<T, P>,
): Option.Option<T> => {
const key = Array.head(path as AnyPath)
if (Option.isNone(key))
return Option.some(value as T)
if (!Predicate.hasProperty(parent, key.value))
return Option.none()
const child = immutableSet<any, any>(parent[key.value], Option.getOrThrow(Array.tail(path as AnyPath)), value)
if (Option.isNone(child))
return child
if (Array.isArray(parent))
return typeof key.value === "number"
? Option.some([
...parent.slice(0, key.value),
child.value,
...parent.slice(key.value + 1),
] as T)
: Option.none()
if (typeof parent === "object")
return Option.some(
Object.assign(
Object.create(Object.getPrototypeOf(parent)),
{ ...parent, [key.value]: child.value },
)
)
return Option.none()
}

View File

@@ -1,4 +1,5 @@
import { Effect, Effectable, Readable, Ref, Stream, Subscribable, SubscriptionRef, SynchronizedRef, type Types, type Unify } from "effect" import { Effect, Effectable, Option, Readable, Ref, Stream, Subscribable, SubscriptionRef, SynchronizedRef, type Types, type Unify } from "effect"
import * as PropertyPath from "./PropertyPath.js"
export const SubscriptionSubRefTypeId: unique symbol = Symbol.for("reffuse/types/SubscriptionSubRef") export const SubscriptionSubRefTypeId: unique symbol = Symbol.for("reffuse/types/SubscriptionSubRef")
@@ -87,3 +88,12 @@ export const makeFromGetSet = <A, B>(
getter: (parentValue: B) => A, getter: (parentValue: B) => A,
setter: (parentValue: B, value: A) => B, setter: (parentValue: B, value: A) => B,
): SubscriptionSubRef<A, B> => new SubscriptionSubRefImpl(parent, getter, setter) ): SubscriptionSubRef<A, B> => new SubscriptionSubRefImpl(parent, getter, setter)
export const makeFromPath = <B, const P extends PropertyPath.Paths<B>>(
parent: SubscriptionRef.SubscriptionRef<B>,
path: P,
): SubscriptionSubRef<PropertyPath.ValueFromPath<B, P>, B> => new SubscriptionSubRefImpl(
parent,
parentValue => Option.getOrThrow(PropertyPath.get(parentValue, path)),
(parentValue, value) => Option.getOrThrow(PropertyPath.immutableSet(parentValue, path, value)),
)

View File

@@ -1,2 +1,3 @@
export * as PropertyPath from "./PropertyPath.js"
export * as SetStateAction from "./SetStateAction.js" export * as SetStateAction from "./SetStateAction.js"
export * as SubscriptionSubRef from "./SubscriptionSubRef.js" export * as SubscriptionSubRef from "./SubscriptionSubRef.js"