import { Chunk, Effect, Effectable, Option, Predicate, 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("@effect-fc/SubscriptionSubRef/SubscriptionSubRef") export type SubscriptionSubRefTypeId = typeof SubscriptionSubRefTypeId export interface SubscriptionSubRef> extends SubscriptionSubRef.Variance, SubscriptionRef.SubscriptionRef { readonly parent: B readonly [Unify.typeSymbol]?: unknown readonly [Unify.unifySymbol]?: SubscriptionSubRefUnify readonly [Unify.ignoreSymbol]?: SubscriptionSubRefUnifyIgnore } export declare namespace SubscriptionSubRef { export interface Variance { readonly [SubscriptionSubRefTypeId]: { readonly _A: Types.Invariant readonly _B: Types.Invariant } } } export interface SubscriptionSubRefUnify extends SubscriptionRef.SubscriptionRefUnify { SubscriptionSubRef?: () => Extract> } export interface SubscriptionSubRefUnifyIgnore extends SubscriptionRef.SubscriptionRefUnifyIgnore { SubscriptionRef?: true } 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: 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) } commit() { return this.get } get changes(): Stream.Stream { return Stream.unwrap( Effect.map(this.get, a => Stream.concat( Stream.make(a), Stream.map(this.parent.changes, this.getter), )) ) } 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", (): Effect.Effect> => this.parent), Effect.bind("ca", ({ b }) => f(this.getter(b))), Effect.tap(({ b, ca: [, a] }) => SubscriptionRef.set(this.parent, this.setter(b, a))), Effect.map(({ ca: [c] }) => c), ) } } export const isSubscriptionSubRef = (u: unknown): u is SubscriptionSubRef> => Predicate.hasProperty(u, SubscriptionSubRefTypeId) export const makeFromGetSet = >( parent: B, options: { 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 = < B extends SubscriptionRef.SubscriptionRef, const P extends PropertyPath.Paths>, >( parent: B, path: P, ): SubscriptionSubRef, P>, B> => new SubscriptionSubRefImpl( parent, parentValue => Option.getOrThrow(PropertyPath.get(parentValue, path)), (parentValue, value) => Option.getOrThrow(PropertyPath.immutableSet(parentValue, path, value)), ) export const makeFromChunkIndex: { >>( parent: B, index: number, ): SubscriptionSubRef< Effect.Effect.Success extends Chunk.NonEmptyChunk ? A : never, B > >>( parent: B, index: number, ): SubscriptionSubRef< Effect.Effect.Success extends Chunk.Chunk ? A : never, B > } = ( parent: SubscriptionRef.SubscriptionRef>, index: number, ) => new SubscriptionSubRefImpl( parent, parentValue => Chunk.unsafeGet(parentValue, index), (parentValue, value) => Chunk.replace(parentValue, index, value), ) as any export const makeFromChunkFindFirst: { >>( parent: B, findFirstPredicate: Predicate.Predicate extends Chunk.NonEmptyChunk ? A : never>, ): SubscriptionSubRef< Effect.Effect.Success extends Chunk.NonEmptyChunk ? A : never, B > >>( parent: B, findFirstPredicate: Predicate.Predicate extends Chunk.Chunk ? A : never>, ): SubscriptionSubRef< Effect.Effect.Success extends Chunk.Chunk ? A : never, B > } = ( parent: SubscriptionRef.SubscriptionRef>, findFirstPredicate: Predicate.Predicate.Any, ) => new SubscriptionSubRefImpl( parent, parentValue => Option.getOrThrow(Chunk.findFirst(parentValue, findFirstPredicate)), (parentValue, value) => Option.getOrThrow(Option.andThen( Chunk.findFirstIndex(parentValue, findFirstPredicate), index => Chunk.replace(parentValue, index, value), )), ) as any export const makeFromChunkFindLast: { >>( parent: B, findLastPredicate: Predicate.Predicate extends Chunk.NonEmptyChunk ? A : never>, ): SubscriptionSubRef< Effect.Effect.Success extends Chunk.NonEmptyChunk ? A : never, B > >>( parent: B, findLastPredicate: Predicate.Predicate extends Chunk.Chunk ? A : never>, ): SubscriptionSubRef< Effect.Effect.Success extends Chunk.Chunk ? A : never, B > } = ( parent: SubscriptionRef.SubscriptionRef>, findLastPredicate: Predicate.Predicate.Any, ) => new SubscriptionSubRefImpl( parent, parentValue => Option.getOrThrow(Chunk.findLast(parentValue, findLastPredicate)), (parentValue, value) => Option.getOrThrow(Option.andThen( Chunk.findLastIndex(parentValue, findLastPredicate), index => Chunk.replace(parentValue, index, value), )), ) as any