Skip to content

Commit e1c5e51

Browse files
reset fn state on cleanup (#371)
Co-authored-by: Tim Smart <[email protected]>
1 parent effcd08 commit e1c5e51

File tree

3 files changed

+83
-38
lines changed

3 files changed

+83
-38
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@effect-atom/atom": patch
3+
---
4+
5+
Ensure atom ttl is 0 for internal intermediate state atoms.

packages/atom/src/Atom.ts

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,28 @@ export interface WriteContext<A> {
167167
set<R, W>(this: WriteContext<A>, atom: Writable<R, W>, value: W): void
168168
}
169169

170+
/**
171+
* @since 1.0.0
172+
* @category combinators
173+
*/
174+
export const setIdleTTL: {
175+
(duration: Duration.DurationInput): <A extends Atom<any>>(self: A) => A
176+
<A extends Atom<any>>(self: A, duration: Duration.DurationInput): A
177+
} = dual<
178+
(duration: Duration.DurationInput) => <A extends Atom<any>>(self: A) => A,
179+
<A extends Atom<any>>(self: A, duration: Duration.DurationInput) => A
180+
>(2, (self, durationInput) => {
181+
const duration = Duration.decode(durationInput)
182+
const isFinite = Duration.isFinite(duration)
183+
return Object.assign(Object.create(Object.getPrototypeOf(self)), {
184+
...self,
185+
keepAlive: !isFinite,
186+
idleTTL: isFinite ? Duration.toMillis(duration) : undefined
187+
})
188+
})
189+
190+
const removeTtl = setIdleTTL(0)
191+
170192
const AtomProto = {
171193
[TypeId]: TypeId,
172194
pipe() {
@@ -217,7 +239,7 @@ const RuntimeProto = {
217239
readonly disableAccumulation?: boolean
218240
readonly initialValue?: ReadonlyArray<any>
219241
}) {
220-
const pullSignal = state(0)
242+
const pullSignal = removeTtl(state(0))
221243
const pullAtom = readable((get) => {
222244
const previous = get.self<Result.Result<any, any>>()
223245
const runtimeResult = get(this)
@@ -236,7 +258,7 @@ const RuntimeProto = {
236258

237259
subscriptionRef(this: AtomRuntime<any, any>, ref: any) {
238260
return makeSubRef(
239-
readable((get) => {
261+
removeTtl(readable((get) => {
240262
const previous = get.self<Result.Result<any, any>>()
241263
const runtimeResult = get(this)
242264
if (runtimeResult._tag !== "Success") {
@@ -246,7 +268,7 @@ const RuntimeProto = {
246268
return SubscriptionRef.SubscriptionRefTypeId in value
247269
? value
248270
: makeEffect(get, value, Result.initial(true), runtimeResult.value)
249-
}),
271+
})),
250272
(get, ref) => {
251273
const runtime = Result.getOrThrow(get(this))
252274
return readSubscribable(get, ref, runtime)
@@ -677,11 +699,11 @@ export const context: (options: {
677699
factory.addGlobalLayer = (layer: Layer.Layer<any, any, AtomRegistry | Reactivity.Reactivity>) => {
678700
globalLayer = Layer.provideMerge(globalLayer, Layer.provide(layer, Reactivity.layer))
679701
}
680-
const reactivityAtom = make(
702+
const reactivityAtom = removeTtl(make(
681703
Effect.scopeWith((scope) => Layer.buildWithMemoMap(Reactivity.layer, options.memoMap, scope)).pipe(
682704
Effect.map(EffectContext.get(Reactivity.Reactivity))
683705
)
684-
)
706+
))
685707
factory.withReactivity =
686708
(keys: ReadonlyArray<unknown> | ReadonlyRecord<string, ReadonlyArray<unknown>>) =>
687709
<A extends Atom<any>>(atom: A): A =>
@@ -1007,7 +1029,7 @@ export const fnSync: {
10071029
const makeFnSync = <Arg, A>(f: (arg: Arg, get: FnContext) => A, options?: {
10081030
readonly initialValue?: A
10091031
}): Writable<Option.Option<A> | A, Arg> => {
1010-
const argAtom = state<[number, Arg]>([0, undefined as any])
1032+
const argAtom = removeTtl(state<[number, Arg]>([0, undefined as any]))
10111033
const hasInitialValue = options?.initialValue !== undefined
10121034
return writable(function(get) {
10131035
;(get as any).isFn = true
@@ -1100,16 +1122,16 @@ function makeResultFn<Arg, E, A>(
11001122
readonly concurrent?: boolean | undefined
11011123
}
11021124
) {
1103-
const argAtom = state<[number, Arg | Interrupt]>([0, undefined as any])
1125+
const argAtom = removeTtl(state<[number, Arg | Interrupt]>([0, undefined as any]))
11041126
const initialValue = options?.initialValue !== undefined
11051127
? Result.success<A, E>(options.initialValue)
11061128
: Result.initial<A, E>()
11071129
const fibersAtom = options?.concurrent
1108-
? make((get) => {
1130+
? removeTtl(readable((get) => {
11091131
const fibers = new Set<Fiber.RuntimeFiber<any, any>>()
11101132
get.addFinalizer(() => fibers.forEach((f) => f.unsafeInterruptAsFork(FiberId.none)))
11111133
return fibers
1112-
})
1134+
}))
11131135
: undefined
11141136

11151137
function read(get: Context, runtime?: Runtime.Runtime<any>): Result.Result<A, E | NoSuchElementException> {
@@ -1202,12 +1224,10 @@ export const pull = <A, E>(
12021224
readonly disableAccumulation?: boolean | undefined
12031225
}
12041226
): Writable<PullResult<A, E>, void> => {
1205-
const pullSignal = state(0)
1206-
const pullAtom = readable(
1207-
makeRead(function(get) {
1208-
return makeStreamPullEffect(get, pullSignal, create, options)
1209-
})
1210-
)
1227+
const pullSignal = removeTtl(state(0))
1228+
const pullAtom = readable(makeRead(function(get) {
1229+
return makeStreamPullEffect(get, pullSignal, create, options)
1230+
}))
12111231
return makeStreamPull(pullSignal, pullAtom)
12121232
}
12131233

@@ -1452,26 +1472,6 @@ export const withLabel: {
14521472
label: [name, new Error().stack?.split("\n")[5] ?? ""]
14531473
}))
14541474

1455-
/**
1456-
* @since 1.0.0
1457-
* @category combinators
1458-
*/
1459-
export const setIdleTTL: {
1460-
(duration: Duration.DurationInput): <A extends Atom<any>>(self: A) => A
1461-
<A extends Atom<any>>(self: A, duration: Duration.DurationInput): A
1462-
} = dual<
1463-
(duration: Duration.DurationInput) => <A extends Atom<any>>(self: A) => A,
1464-
<A extends Atom<any>>(self: A, duration: Duration.DurationInput) => A
1465-
>(2, (self, durationInput) => {
1466-
const duration = Duration.decode(durationInput)
1467-
const isFinite = Duration.isFinite(duration)
1468-
return Object.assign(Object.create(Object.getPrototypeOf(self)), {
1469-
...self,
1470-
keepAlive: !isFinite,
1471-
idleTTL: isFinite ? Duration.toMillis(duration) : undefined
1472-
})
1473-
})
1474-
14751475
/**
14761476
* @since 1.0.0
14771477
* @category combinators
@@ -1593,12 +1593,12 @@ export const debounce: {
15931593
*/
15941594
export const optimistic = <A>(self: Atom<A>): Writable<A, Atom<Result.Result<A, unknown>>> => {
15951595
let counter = 0
1596-
const writeAtom = state(
1596+
const writeAtom = removeTtl(state(
15971597
[
15981598
counter,
15991599
undefined as any as Atom<Result.Result<A, unknown>>
16001600
] as const
1601-
)
1601+
))
16021602
return writable(
16031603
(get) => {
16041604
let lastValue = get.once(self)
@@ -1714,7 +1714,7 @@ export const optimisticFn: {
17141714
| ((set: (result: NoInfer<W>) => void) => AtomResultFn<OW, XA, XE>)
17151715
}
17161716
): AtomResultFn<OW, XA, XE> => {
1717-
const transition = state<Result.Result<W, unknown>>(Result.initial())
1717+
const transition = removeTtl(state<Result.Result<W, unknown>>(Result.initial()))
17181718
return fn((arg: OW, get) => {
17191719
let value = options.reducer(get(self), arg)
17201720
if (Result.isResult(value)) {

packages/atom/test/Atom.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,46 @@ describe("Atom", () => {
748748
expect(r.get(state3)).toEqual(0)
749749
})
750750

751+
it("idleTTL fn", async () => {
752+
const fn = Atom.fn((n: number) => Effect.succeed(n + 1)).pipe(
753+
Atom.setIdleTTL(0)
754+
)
755+
const r = Registry.make({ defaultIdleTTL: 2000 })
756+
757+
let result = r.get(fn)
758+
assert(Result.isInitial(result))
759+
760+
r.set(fn, 1)
761+
result = r.get(fn)
762+
assert(Result.isSuccess(result))
763+
expect(result.value).toEqual(2)
764+
765+
await new Promise((resolve) => resolve(null))
766+
767+
result = r.get(fn)
768+
assert(Result.isInitial(result))
769+
})
770+
771+
it("idleTTL fnSync", async () => {
772+
const fn = Atom.fnSync((n: number) => n + 1).pipe(
773+
Atom.setIdleTTL(0)
774+
)
775+
const r = Registry.make({ defaultIdleTTL: 2000 })
776+
777+
let result = r.get(fn)
778+
assert(Option.isNone(result))
779+
780+
r.set(fn, 1)
781+
result = r.get(fn)
782+
assert(Option.isSome(result))
783+
expect(result.value).toEqual(2)
784+
785+
await new Promise((resolve) => resolve(null))
786+
787+
result = r.get(fn)
788+
assert(Option.isNone(result))
789+
})
790+
751791
it("fn", async () => {
752792
const count = Atom.fnSync((n: number) => n).pipe(Atom.keepAlive)
753793
const r = Registry.make()

0 commit comments

Comments
 (0)