Skip to content

Commit

Permalink
Rx.family
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart committed Sep 20, 2023
1 parent 9f2fe7f commit c8d1905
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/lovely-parents-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect-rx/rx": patch
---

Rx.family
11 changes: 11 additions & 0 deletions docs/rx/Rx.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Added in v1.0.0
- [constructors](#constructors)
- [effect](#effect)
- [effectFn](#effectfn)
- [family](#family)
- [fn](#fn)
- [readable](#readable)
- [runtime](#runtime)
Expand Down Expand Up @@ -137,6 +138,16 @@ export declare const effectFn: {
Added in v1.0.0
## family
**Signature**
```ts
export declare const family: <Arg, T extends Rx<any>>(f: (arg: Arg) => T) => (arg: Arg) => T
```
Added in v1.0.0
## fn
**Signature**
Expand Down
24 changes: 24 additions & 0 deletions packages/rx/src/Rx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,30 @@ export const streamPull: {
})
}

/**
* @since 1.0.0
* @category constructors
*/
export const family = <Arg, T extends Rx<any>>(
f: (arg: Arg) => T
): (arg: Arg) => T => {
const atoms = new Map<number, WeakRef<T>>()
const registry = new FinalizationRegistry<number>((hash) => {
atoms.delete(hash)
})
return function(arg) {
const hash = Hash.hash(arg)
const atom = atoms.get(hash)?.deref()
if (atom !== undefined) {
return atom
}
const newAtom = f(arg)
atoms.set(hash, new WeakRef(newAtom))
registry.register(newAtom, hash)
return newAtom
}
}

/**
* @since 1.0.0
* @category combinators
Expand Down
2 changes: 1 addition & 1 deletion packages/rx/src/internal/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class RegistryImpl implements Registry.Registry {
this[TypeId] = TypeId
}

private readonly nodes = new WeakMap<Rx.Rx<any>, Node<any>>()
private readonly nodes = new Map<Rx.Rx<any>, Node<any>>()

get = <A>(rx: Rx.Rx<A>): A => {
return this.ensureNode(rx).value()
Expand Down
24 changes: 24 additions & 0 deletions packages/rx/test/Rx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as Registry from "@effect-rx/rx/Registry"
import * as Result from "@effect-rx/rx/Result"
import * as Rx from "@effect-rx/rx/Rx"
import * as Context from "@effect/data/Context"
import * as Hash from "@effect/data/Hash"
import * as Option from "@effect/data/Option"
import * as Effect from "@effect/io/Effect"
import * as Layer from "@effect/io/Layer"
Expand Down Expand Up @@ -221,6 +222,29 @@ describe("Rx", () => {
assert(Result.isWaiting(result))
assert(Option.isNone(Result.value(result)))
})

it("family", async () => {
const r = Registry.make()

const count = Rx.family((n: number) => Rx.state(n))
const hash = Hash.hash(count(1))
assert.strictEqual(count(1), count(1))
r.set(count(1), 2)
assert.strictEqual(r.get(count(1)), 2)

const countKeep = Rx.family((n: number) => Rx.state(n).pipe(Rx.keepAlive))
assert.strictEqual(countKeep(1), countKeep(1))
r.get(countKeep(1))
const hashKeep = Hash.hash(countKeep(1))

if (global.gc) {
vi.useRealTimers()
await new Promise((resolve) => setTimeout(resolve, 0))
global.gc()
assert.notEqual(hash, Hash.hash(count(1)))
assert.strictEqual(hashKeep, Hash.hash(countKeep(1)))
}
})
})

interface Counter {
Expand Down

0 comments on commit c8d1905

Please sign in to comment.