Refactor Subscribable
Lint / lint (push) Successful in 45s

This commit is contained in:
Julien Valverdé
2026-06-21 03:09:41 +02:00
parent fdb2bbfd00
commit 364536b35a
4 changed files with 53 additions and 34 deletions
+3 -3
View File
@@ -126,12 +126,12 @@ yield* Lens.updateSomeEffect(lens, (value) =>
Effect v4 removed its `Readable` and `Subscribable` modules. This package provides a small compatible abstraction with `get` and `changes` properties: Effect v4 removed its `Readable` and `Subscribable` modules. This package provides a small compatible abstraction with `get` and `changes` properties:
```ts ```ts
import { Subscribable } from "effect-lens-next" import { Lens, Subscribable } from "effect-lens-next"
const users = Subscribable.fromSubscriptionRef(usersRef) const users = Lens.fromSubscriptionRef(usersRef)
const count = Subscribable.focusArrayLength(users) const count = Subscribable.focusArrayLength(users)
const current = yield* count.get const current = yield* count.get
``` ```
The module includes `make`, `map`, `mapEffect`, `unwrap`, `fromSubscriptionRef`, and the same readonly focus helpers used by Lens. The module includes `make`, `map`, `mapEffect`, `unwrap`, and the same readonly focus helpers used by Lens. A `SubscriptionRef` can first be converted with `Lens.fromSubscriptionRef`, since every Lens is already a Subscribable.
+1 -1
View File
@@ -66,7 +66,7 @@ export declare namespace LensImpl {
export abstract class LensImpl<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never> export abstract class LensImpl<in out A, in out ER = never, in out EW = never, in out RR = never, in out RW = never>
extends Pipeable.Class implements Lens<A, ER, EW, RR, RW> { extends Pipeable.Class implements Lens<A, ER, EW, RR, RW> {
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId readonly [Subscribable.SubscribableTypeId]: Subscribable.SubscribableTypeId = Subscribable.SubscribableTypeId
readonly [LensTypeId]: LensTypeId = LensTypeId readonly [LensTypeId]: LensTypeId = LensTypeId
readonly [LensImplTypeId]: LensImplTypeId = LensImplTypeId readonly [LensImplTypeId]: LensImplTypeId = LensImplTypeId
@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { Chunk, Effect, SubscriptionRef } from "effect" import { Chunk, Effect, SubscriptionRef } from "effect"
import * as Lens from "./Lens.js"
import * as Subscribable from "./Subscribable.js" import * as Subscribable from "./Subscribable.js"
@@ -9,7 +10,7 @@ describe("Subscribable", () => {
Effect.flatMap( Effect.flatMap(
SubscriptionRef.make([1, 2, 3]), SubscriptionRef.make([1, 2, 3]),
parent => { parent => {
const sizeSub = Subscribable.focusArrayLength(Subscribable.fromSubscriptionRef(parent)) const sizeSub = Subscribable.focusArrayLength(Lens.fromSubscriptionRef(parent))
return Effect.flatMap( return Effect.flatMap(
sizeSub.get, sizeSub.get,
initial => Effect.flatMap( initial => Effect.flatMap(
@@ -29,7 +30,7 @@ describe("Subscribable", () => {
Effect.flatMap( Effect.flatMap(
SubscriptionRef.make(Chunk.make(1, 2) as Chunk.Chunk<number>), SubscriptionRef.make(Chunk.make(1, 2) as Chunk.Chunk<number>),
parent => { parent => {
const sizeSub = Subscribable.focusChunkSize(Subscribable.fromSubscriptionRef(parent)) const sizeSub = Subscribable.focusChunkSize(Lens.fromSubscriptionRef(parent))
return Effect.flatMap( return Effect.flatMap(
sizeSub.get, sizeSub.get,
initial => Effect.flatMap( initial => Effect.flatMap(
@@ -49,7 +50,7 @@ describe("Subscribable", () => {
Effect.flatMap( Effect.flatMap(
SubscriptionRef.make([1, 2, 3]), SubscriptionRef.make([1, 2, 3]),
parent => { parent => {
const sizeSub = Subscribable.focusIterableSize(Subscribable.fromSubscriptionRef(parent)) const sizeSub = Subscribable.focusIterableSize(Lens.fromSubscriptionRef(parent))
return Effect.flatMap( return Effect.flatMap(
sizeSub.get, sizeSub.get,
initial => Effect.flatMap( initial => Effect.flatMap(
+45 -27
View File
@@ -9,43 +9,68 @@ import {
Pipeable, Pipeable,
Predicate, Predicate,
Stream, Stream,
SubscriptionRef,
} from "effect" } from "effect"
export const TypeId: unique symbol = Symbol.for("@effect-fc/Lens/v4/Subscribable") export const SubscribableTypeId: unique symbol = Symbol.for("@effect-fc/Lens/v4/Subscribable")
export type TypeId = typeof TypeId export type SubscribableTypeId = typeof SubscribableTypeId
export interface Subscribable<in out A, in out E = never, in out R = never> extends Pipeable.Pipeable { export interface Subscribable<in out A, in out E = never, in out R = never> extends Pipeable.Pipeable {
readonly [TypeId]: TypeId readonly [SubscribableTypeId]: SubscribableTypeId
readonly get: Effect.Effect<A, E, R> readonly get: Effect.Effect<A, E, R>
readonly changes: Stream.Stream<A, E, R> readonly changes: Stream.Stream<A, E, R>
} }
class SubscribableImpl<in out A, in out E, in out R> export const isSubscribable = (u: unknown): u is Subscribable<unknown, unknown, unknown> => Predicate.hasProperty(u, SubscribableTypeId)
extends Pipeable.Class implements Subscribable<A, E, R> {
readonly [TypeId]: TypeId = TypeId
constructor(
readonly get: Effect.Effect<A, E, R>, export const SubscribableImplTypeId: unique symbol = Symbol.for("@effect-fc/Lens/v4/SubscribableImpl")
readonly changes: Stream.Stream<A, E, R>, export type SubscribableImplTypeId = typeof SubscribableImplTypeId
) {
super() export declare namespace SubscribableImpl {
export interface Source<in out A, in out E = never, in out R = never> {
readonly get: Effect.Effect<A, E, R>
readonly changes: Stream.Stream<A, E, R>
} }
} }
export const isSubscribable = (u: unknown): u is Subscribable<unknown, unknown, unknown> => Predicate.hasProperty(u, TypeId) export class SubscribableImpl<in out A, in out E = never, in out R = never>
extends Pipeable.Class implements Subscribable<A, E, R> {
readonly [SubscribableTypeId]: SubscribableTypeId = SubscribableTypeId
readonly [SubscribableImplTypeId]: SubscribableImplTypeId = SubscribableImplTypeId
export const make = <A, E, R>(options: { constructor(
readonly get: Effect.Effect<A, E, R> readonly source: SubscribableImpl.Source<A, E, R>,
readonly changes: Stream.Stream<A, E, R> ) {
}): Subscribable<A, E, R> => new SubscribableImpl(options.get, options.changes) super()
}
export const fromSubscriptionRef = <A>(self: SubscriptionRef.SubscriptionRef<A>): Subscribable<A> => make({ get get() { return this.source.get }
get: SubscriptionRef.get(self), get changes() { return this.source.changes }
changes: SubscriptionRef.changes(self), }
export const isSubscribableImpl = (u: unknown): u is SubscribableImpl<unknown, unknown, unknown> => Predicate.hasProperty(u, SubscribableImplTypeId)
export const asSubscribableImpl = <A, E, R>(
subscribable: Subscribable<A, E, R>
): SubscribableImpl<A, E, R> => {
if (!isSubscribableImpl(subscribable))
throw new Error("Not a 'SubscribableImpl'")
return subscribable as SubscribableImpl<A, E, R>
}
export const make = <A, E, R>(
source: SubscribableImpl.Source<A, E, R>
): Subscribable<A, E, R> => new SubscribableImpl(source)
export const unwrap = <A, E, R, E1, R1>(
effect: Effect.Effect<Subscribable<A, E, R>, E1, R1>,
): Subscribable<A, E | E1, R | R1> => make({
get: Effect.flatMap(effect, self => self.get),
changes: Stream.unwrap(Effect.map(effect, self => self.changes)),
}) })
export const map: { export const map: {
<A, B>(f: (a: NoInfer<A>) => B): <E, R>(self: Subscribable<A, E, R>) => Subscribable<B, E, R> <A, B>(f: (a: NoInfer<A>) => B): <E, R>(self: Subscribable<A, E, R>) => Subscribable<B, E, R>
<A, E, R, B>(self: Subscribable<A, E, R>, f: (a: NoInfer<A>) => B): Subscribable<B, E, R> <A, E, R, B>(self: Subscribable<A, E, R>, f: (a: NoInfer<A>) => B): Subscribable<B, E, R>
@@ -65,13 +90,6 @@ export const mapEffect: {
changes: Stream.mapEffect(self.changes, f), changes: Stream.mapEffect(self.changes, f),
})) }))
export const unwrap = <A, E, R, E1, R1>(
effect: Effect.Effect<Subscribable<A, E, R>, E1, R1>,
): Subscribable<A, E | E1, R | R1> => make({
get: Effect.flatMap(effect, self => self.get),
changes: Stream.unwrap(Effect.map(effect, self => self.changes)),
})
/** Maps over an `Option` value in the `Subscribable`. */ /** Maps over an `Option` value in the `Subscribable`. */
export const mapOption: { export const mapOption: {
<A, B>(f: (a: A) => B): <E, R>(self: Subscribable<Option.Option<A>, E, R>) => Subscribable<Option.Option<B>, E, R> <A, B>(f: (a: A) => B): <E, R>(self: Subscribable<Option.Option<A>, E, R>) => Subscribable<Option.Option<B>, E, R>