From cd18a9d1086592473c148e789f9ee13829f158c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Sun, 11 Jan 2026 13:08:16 +0100 Subject: [PATCH] Query work --- packages/effect-fc/src/Query.ts | 33 ++++++++++++++------------- packages/effect-fc/src/QueryClient.ts | 10 +++++++- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/effect-fc/src/Query.ts b/packages/effect-fc/src/Query.ts index 697f2a6..c3a1933 100644 --- a/packages/effect-fc/src/Query.ts +++ b/packages/effect-fc/src/Query.ts @@ -1,4 +1,4 @@ -import { type Cause, type Context, DateTime, Duration, Effect, Exit, Fiber, HashMap, identity, Option, Pipeable, Predicate, type Scope, Stream, type Subscribable, SubscriptionRef } from "effect" +import { type Cause, type Context, DateTime, type Duration, Effect, Exit, Fiber, HashMap, identity, Option, Pipeable, Predicate, type Scope, Stream, Subscribable, SubscriptionRef } from "effect" import * as QueryClient from "./QueryClient.js" import * as Result from "./Result.js" @@ -58,7 +58,7 @@ extends Pipeable.Class() implements Query { get run(): Effect.Effect { return Stream.runForEach(this.key, key => this.interrupt.pipe( Effect.andThen(SubscriptionRef.set(this.latestKey, Option.some(key))), - Effect.andThen(this.start(key, Result.initial(), false)), + Effect.andThen(this.startCached(key, Result.initial(), false)), Effect.andThen(sub => Effect.forkScoped(this.watch(sub))), Effect.provide(this.context), this.runSemaphore.withPermits(1), @@ -75,14 +75,14 @@ extends Pipeable.Class() implements Query { fetch(key: K): Effect.Effect> { return this.interrupt.pipe( Effect.andThen(SubscriptionRef.set(this.latestKey, Option.some(key))), - Effect.andThen(Effect.provide(this.start(key, Result.initial(), false), this.context)), + Effect.andThen(Effect.provide(this.startCached(key, Result.initial(), false), this.context)), Effect.andThen(sub => this.watch(sub)), ) } fetchSubscribable(key: K): Effect.Effect>> { return this.interrupt.pipe( Effect.andThen(SubscriptionRef.set(this.latestKey, Option.some(key))), - Effect.andThen(Effect.provide(this.start(key, Result.initial(), false), this.context)), + Effect.andThen(Effect.provide(this.startCached(key, Result.initial(), false), this.context)), ) } get refetch(): Effect.Effect, Cause.NoSuchElementException> { @@ -118,24 +118,25 @@ extends Pipeable.Class() implements Query { startCached( key: K, + initial: Result.Result, refresh: boolean, ): Effect.Effect< Subscribable.Subscribable>, never, Scope.Scope | QueryClient.QueryClient | R > { - return this.getCacheEntry(key).pipe( - Effect.andThen(Option.match({ - onSome: entry => Effect.andThen( - DateTime.now, - now => Duration.lessThan(DateTime.distanceDuration(entry.createdAt, now), this.staleTime) - ? Result.optimistic(entry.result) as Result.Result - : Result.initial(), - ), - onNone: () => Effect.succeed(Result.initial()), - })), - Effect.andThen(initial => this.start(key, initial, refresh)), - ) + return Effect.andThen(this.getCacheEntry(key), Option.match({ + onSome: entry => QueryClient.isQueryClientCacheEntryStale(entry, this.staleTime) + ? Effect.map( + SubscriptionRef.set(this.result, entry.result as Result.Result), + () => Subscribable.make({ + get: Effect.succeed(entry.result as Result.Result), + get changes() { return Stream.empty }, + }), + ) + : this.start(key, Result.optimistic(entry.result) as Result.Result, false), + onNone: () => this.start(key, initial, refresh), + })) } start( diff --git a/packages/effect-fc/src/QueryClient.ts b/packages/effect-fc/src/QueryClient.ts index 31a4910..4e931cb 100644 --- a/packages/effect-fc/src/QueryClient.ts +++ b/packages/effect-fc/src/QueryClient.ts @@ -1,4 +1,4 @@ -import { type DateTime, type Duration, Effect, Equal, Equivalence, Hash, HashMap, Pipeable, Predicate, type Scope, SubscriptionRef } from "effect" +import { DateTime, Duration, Effect, Equal, Equivalence, Hash, HashMap, Pipeable, Predicate, type Scope, SubscriptionRef } from "effect" import type * as Query from "./Query.js" import type * as Result from "./Result.js" @@ -105,3 +105,11 @@ implements Pipeable.Pipeable { } export const isQueryClientCacheEntry = (u: unknown): u is QueryClientCacheEntry => Predicate.hasProperty(u, QueryClientCacheEntryTypeId) + +export const isQueryClientCacheEntryStale = ( + self: QueryClientCacheEntry, + staleTime: Duration.DurationInput, +): Effect.Effect => Effect.andThen( + DateTime.now, + now => Duration.lessThan(DateTime.distanceDuration(self.createdAt, now), staleTime), +)