From cb5e3a84ae0d13ce099a1be04f2833a173e7c2a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Thu, 11 Dec 2025 06:14:24 +0100 Subject: [PATCH] QueryClient work --- packages/effect-fc/src/Form.ts | 4 +- packages/effect-fc/src/QueryClient.ts | 84 ++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/packages/effect-fc/src/Form.ts b/packages/effect-fc/src/Form.ts index 53785aa..0af8b04 100644 --- a/packages/effect-fc/src/Form.ts +++ b/packages/effect-fc/src/Form.ts @@ -121,7 +121,7 @@ extends Pipeable.Class() implements Form { export const isForm = (u: unknown): u is Form => Predicate.hasProperty(u, FormTypeId) -export namespace make { +export declare namespace make { export interface Options extends Mutation.make.Options< readonly [value: NoInfer, form: Form, NoInfer, NoInfer, unknown, unknown, unknown>], @@ -203,7 +203,7 @@ export const run = ( )) } -export namespace service { +export declare namespace service { export interface Options extends make.Options {} } diff --git a/packages/effect-fc/src/QueryClient.ts b/packages/effect-fc/src/QueryClient.ts index 43f9984..1c2faa5 100644 --- a/packages/effect-fc/src/QueryClient.ts +++ b/packages/effect-fc/src/QueryClient.ts @@ -1,43 +1,97 @@ -import { Equal, Equivalence, Hash, type HashMap, Pipeable, Predicate, type Ref } from "effect" +import { type DateTime, type Duration, Effect, Equal, Equivalence, Hash, HashMap, Pipeable, Predicate, Ref, type Scope } from "effect" import type * as Query from "./Query.js" import type * as Result from "./Result.js" -export const QueryClientTypeId: unique symbol = Symbol.for("@effect-fc/QueryClient/QueryClient") -export type QueryClientTypeId = typeof QueryClientTypeId - -export interface QueryClient extends Pipeable.Pipeable { - readonly [QueryClientTypeId]: QueryClientTypeId +export const QueryClientServiceTypeId: unique symbol = Symbol.for("@effect-fc/QueryClient/QueryClientServiceTypeId") +export type QueryClientServiceTypeId = typeof QueryClientServiceTypeId +export interface QueryClientService extends Pipeable.Pipeable { + readonly [QueryClientServiceTypeId]: QueryClientServiceTypeId readonly cache: Ref.Ref>> + readonly defaultTtl: Duration.DurationInput } -export class QueryClientImpl extends Pipeable.Class() implements QueryClient { - readonly [QueryClientTypeId]: QueryClientTypeId = QueryClientTypeId +export class QueryClient extends Effect.Service()("@effect-fc/QueryClient/QueryClient", { + scoped: Effect.suspend(() => service()) +}) {} + +export class QueryClientServiceImpl extends Pipeable.Class() implements QueryClientService { + readonly [QueryClientServiceTypeId]: QueryClientServiceTypeId = QueryClientServiceTypeId constructor( - readonly cache: Ref.Ref>> + readonly cache: Ref.Ref>>, + readonly defaultTtl: Duration.DurationInput, ) { super() } } -export const isQueryClient = (u: unknown): u is QueryClient => Predicate.hasProperty(u, QueryClientTypeId) +export const isQueryClientService = (u: unknown): u is QueryClientService => Predicate.hasProperty(u, QueryClientServiceTypeId) + +export declare namespace make { + export interface Options { + readonly defaultTtl?: Duration.DurationInput + } +} + +export const make = Effect.fnUntraced(function* (options: make.Options = {}): Effect.fn.Return { + return new QueryClientServiceImpl( + yield* Ref.make(HashMap.empty>()), + options.defaultTtl ?? "5 minutes", + ) +}) + +export const run = (_self: QueryClientService): Effect.Effect => Effect.void + +export declare namespace service { + export interface Options extends make.Options {} +} + +export const service = (options?: service.Options): Effect.Effect => Effect.tap( + make(options), + client => Effect.forkScoped(run(client)), +) -const QueryClientCacheKeyTypeId: unique symbol = Symbol.for("@effect-fc/QueryClient/QueryClientCacheKey") -type QueryClientCacheKeyTypeId = typeof QueryClientCacheKeyTypeId +export const QueryClientCacheKeyTypeId: unique symbol = Symbol.for("@effect-fc/QueryClient/QueryClientCacheKey") +export type QueryClientCacheKeyTypeId = typeof QueryClientCacheKeyTypeId -class QueryClientCacheKey implements Equal.Equal { +export class QueryClientCacheKey implements Equal.Equal { readonly [QueryClientCacheKeyTypeId]: QueryClientCacheKeyTypeId = QueryClientCacheKeyTypeId constructor(readonly key: Query.Query.AnyKey) {} [Equal.symbol](that: Equal.Equal) { - return isQueryClientKey(that) && Equivalence.array(Equal.equivalence())(this.key, that.key) + return isQueryClientCacheKey(that) && Equivalence.array(Equal.equivalence())(this.key, that.key) } [Hash.symbol]() { return Hash.array(this.key) } } -const isQueryClientKey = (u: unknown): u is QueryClientCacheKey => Predicate.hasProperty(u, QueryClientCacheKeyTypeId) +export const isQueryClientCacheKey = (u: unknown): u is QueryClientCacheKey => Predicate.hasProperty(u, QueryClientCacheKeyTypeId) + + +export const QueryClientCacheEntryTypeId: unique symbol = Symbol.for("@effect-fc/QueryClient/QueryClientCacheEntry") +export type QueryClientCacheEntryTypeId = typeof QueryClientCacheEntryTypeId + +export interface QueryClientCacheEntry extends Pipeable.Pipeable { + readonly [QueryClientCacheEntryTypeId]: QueryClientCacheEntryTypeId + readonly at: DateTime.DateTime + readonly ttl: Duration.DurationInput + readonly result: Result.Final +} + +export class QueryClientCacheEntryImpl extends Pipeable.Class() implements QueryClientCacheEntry { + readonly [QueryClientCacheEntryTypeId]: QueryClientCacheEntryTypeId = QueryClientCacheEntryTypeId + + constructor( + readonly at: DateTime.DateTime, + readonly ttl: Duration.DurationInput, + readonly result: Result.Final, + ) { + super() + } +} + +export const isQueryClientCacheEntry = (u: unknown): u is QueryClientCacheEntry => Predicate.hasProperty(u, QueryClientCacheEntryTypeId)