|
|
@@ -1,44 +1,35 @@
|
|
|
|
import {
|
|
|
|
import { type Cause, type Context, Duration, Effect, Equal, Exit, Fiber, Option, Pipeable, Predicate, type Scope, Semaphore, Stream, SubscriptionRef } from "effect"
|
|
|
|
type Cause,
|
|
|
|
import { AsyncResult } from "effect/unstable/reactivity"
|
|
|
|
type Context,
|
|
|
|
import * as Lens from "./Lens.js"
|
|
|
|
type Duration,
|
|
|
|
|
|
|
|
Effect,
|
|
|
|
|
|
|
|
Equal,
|
|
|
|
|
|
|
|
Fiber,
|
|
|
|
|
|
|
|
Option,
|
|
|
|
|
|
|
|
Pipeable,
|
|
|
|
|
|
|
|
Predicate,
|
|
|
|
|
|
|
|
type Scope,
|
|
|
|
|
|
|
|
Semaphore,
|
|
|
|
|
|
|
|
Stream,
|
|
|
|
|
|
|
|
SubscriptionRef,
|
|
|
|
|
|
|
|
} from "effect"
|
|
|
|
|
|
|
|
import { Subscribable } from "effect-lens"
|
|
|
|
|
|
|
|
import * as QueryClient from "./QueryClient.js"
|
|
|
|
import * as QueryClient from "./QueryClient.js"
|
|
|
|
import * as Result from "./Result.js"
|
|
|
|
import * as Subscribable from "./Subscribable.js"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const QueryTypeId: unique symbol = Symbol.for("@effect-fc/Query/Query")
|
|
|
|
export const QueryTypeId: unique symbol = Symbol.for("@effect-fc/Query/Query")
|
|
|
|
export type QueryTypeId = typeof QueryTypeId
|
|
|
|
export type QueryTypeId = typeof QueryTypeId
|
|
|
|
|
|
|
|
|
|
|
|
export interface Query<in out K extends Query.AnyKey, in out A, in out KE = never, in out KR = never, in out E = never, in out R = never, in out P = never>
|
|
|
|
export interface Query<in out K extends Query.AnyKey, in out A, in out KE = never, in out KR = never, in out E = never, in out R = never>
|
|
|
|
extends Pipeable.Pipeable {
|
|
|
|
extends Pipeable.Pipeable {
|
|
|
|
readonly [QueryTypeId]: QueryTypeId
|
|
|
|
readonly [QueryTypeId]: QueryTypeId
|
|
|
|
|
|
|
|
|
|
|
|
readonly context: Context.Context<Scope.Scope | QueryClient.QueryClient | KR | R>
|
|
|
|
readonly context: Context.Context<Scope.Scope | QueryClient.QueryClient | KR | R>
|
|
|
|
readonly key: Stream.Stream<K, KE, KR>
|
|
|
|
readonly key: Stream.Stream<K, KE, KR>
|
|
|
|
readonly f: (key: K) => Effect.Effect<A, E, R>
|
|
|
|
readonly f: (key: K) => Effect.Effect<A, E, R>
|
|
|
|
readonly initialProgress: P
|
|
|
|
|
|
|
|
readonly staleTime: Duration.Input
|
|
|
|
readonly staleTime: Duration.Duration
|
|
|
|
readonly refreshOnWindowFocus: boolean
|
|
|
|
readonly refreshOnWindowFocus: boolean
|
|
|
|
|
|
|
|
|
|
|
|
readonly latestKey: Subscribable.Subscribable<Option.Option<K>>
|
|
|
|
readonly latestKey: Subscribable.Subscribable<Option.Option<K>>
|
|
|
|
readonly fiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, E>>>
|
|
|
|
readonly fiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, E>>>
|
|
|
|
readonly result: Subscribable.Subscribable<Result.Result<A, E, P>>
|
|
|
|
readonly result: Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>>
|
|
|
|
readonly latestFinalResult: Subscribable.Subscribable<Option.Option<Result.Final<A, E, P>>>
|
|
|
|
readonly latestFinalResult: Subscribable.Subscribable<Option.Option<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>>>
|
|
|
|
|
|
|
|
|
|
|
|
readonly run: Effect.Effect<void>
|
|
|
|
readonly run: Effect.Effect<void>
|
|
|
|
fetch(key: K): Effect.Effect<Result.Final<A, E, P>>
|
|
|
|
fetch(key: K): Effect.Effect<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>, Cause.NoSuchElementError>
|
|
|
|
fetchSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>>
|
|
|
|
fetchSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>>, Cause.NoSuchElementError>
|
|
|
|
readonly refresh: Effect.Effect<Result.Final<A, E, P>, Cause.NoSuchElementError>
|
|
|
|
readonly refresh: Effect.Effect<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>, Cause.NoSuchElementError>
|
|
|
|
readonly refreshSubscribable: Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>, Cause.NoSuchElementError>
|
|
|
|
readonly refreshSubscribable: Effect.Effect<Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>>, Cause.NoSuchElementError>
|
|
|
|
|
|
|
|
|
|
|
|
readonly invalidateCache: Effect.Effect<void>
|
|
|
|
readonly invalidateCache: Effect.Effect<void>
|
|
|
|
invalidateCacheEntry(key: K): Effect.Effect<void>
|
|
|
|
invalidateCacheEntry(key: K): Effect.Effect<void>
|
|
|
|
}
|
|
|
|
}
|
|
|
@@ -47,42 +38,43 @@ export declare namespace Query {
|
|
|
|
export type AnyKey = readonly any[]
|
|
|
|
export type AnyKey = readonly any[]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export class QueryImpl<in out K extends Query.AnyKey, in out A, in out KE = never, in out KR = never, in out E = never, in out R = never, in out P = never>
|
|
|
|
export class QueryImpl<in out K extends Query.AnyKey, in out A, in out KE = never, in out KR = never, in out E = never, in out R = never>
|
|
|
|
extends Pipeable.Class implements Query<K, A, KE, KR, E, R, P> {
|
|
|
|
extends Pipeable.Class implements Query<K, A, KE, KR, E, R> {
|
|
|
|
readonly [QueryTypeId]: QueryTypeId = QueryTypeId
|
|
|
|
readonly [QueryTypeId]: QueryTypeId = QueryTypeId
|
|
|
|
readonly latestKey: Subscribable.Subscribable<Option.Option<K>>
|
|
|
|
|
|
|
|
readonly fiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, E>>>
|
|
|
|
|
|
|
|
readonly result: Subscribable.Subscribable<Result.Result<A, E, P>>
|
|
|
|
|
|
|
|
readonly latestFinalResult: Subscribable.Subscribable<Option.Option<Result.Final<A, E, P>>>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
constructor(
|
|
|
|
readonly context: Context.Context<Scope.Scope | QueryClient.QueryClient | KR | R>,
|
|
|
|
readonly context: Context.Context<Scope.Scope | QueryClient.QueryClient | KR | R>,
|
|
|
|
readonly key: Stream.Stream<K, KE, KR>,
|
|
|
|
readonly key: Stream.Stream<K, KE, KR>,
|
|
|
|
readonly f: (key: K) => Effect.Effect<A, E, R>,
|
|
|
|
readonly f: (key: K) => Effect.Effect<A, E, R>,
|
|
|
|
readonly initialProgress: P,
|
|
|
|
|
|
|
|
readonly staleTime: Duration.Input,
|
|
|
|
readonly staleTime: Duration.Duration,
|
|
|
|
readonly refreshOnWindowFocus: boolean,
|
|
|
|
readonly refreshOnWindowFocus: boolean,
|
|
|
|
readonly latestKeyRef: SubscriptionRef.SubscriptionRef<Option.Option<K>>,
|
|
|
|
|
|
|
|
readonly fiberRef: SubscriptionRef.SubscriptionRef<Option.Option<Fiber.Fiber<A, E>>>,
|
|
|
|
readonly latestKey: Lens.Lens<Option.Option<K>>,
|
|
|
|
readonly resultRef: SubscriptionRef.SubscriptionRef<Result.Result<A, E, P>>,
|
|
|
|
readonly fiber: Lens.Lens<Option.Option<Fiber.Fiber<A, E>>>,
|
|
|
|
readonly latestFinalResultRef: SubscriptionRef.SubscriptionRef<Option.Option<Result.Final<A, E, P>>>,
|
|
|
|
readonly result: Lens.Lens<AsyncResult.AsyncResult<A, E>>,
|
|
|
|
|
|
|
|
readonly latestFinalResult: Lens.Lens<Option.Option<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>>>,
|
|
|
|
|
|
|
|
|
|
|
|
readonly runSemaphore: Semaphore.Semaphore,
|
|
|
|
readonly runSemaphore: Semaphore.Semaphore,
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
super()
|
|
|
|
super()
|
|
|
|
this.latestKey = fromSubscriptionRef(latestKeyRef)
|
|
|
|
|
|
|
|
this.fiber = fromSubscriptionRef(fiberRef)
|
|
|
|
|
|
|
|
this.result = fromSubscriptionRef(resultRef)
|
|
|
|
|
|
|
|
this.latestFinalResult = fromSubscriptionRef(latestFinalResultRef)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get run(): Effect.Effect<void> {
|
|
|
|
get run(): Effect.Effect<void> {
|
|
|
|
const focus = this.refreshOnWindowFocus && typeof window !== "undefined"
|
|
|
|
|
|
|
|
? Stream.runForEach(Stream.fromEventListener<FocusEvent>(window, "focus"), () => this.refreshSubscribable)
|
|
|
|
|
|
|
|
: Effect.succeed(undefined)
|
|
|
|
|
|
|
|
return Effect.all([
|
|
|
|
return Effect.all([
|
|
|
|
Stream.runForEach(this.key, key => this.fetchSubscribable(key)),
|
|
|
|
Stream.runForEach(this.key, key => this.fetchSubscribable(key)),
|
|
|
|
focus,
|
|
|
|
|
|
|
|
], { concurrency: "unbounded", discard: true }).pipe(
|
|
|
|
Effect.promise(() => import("@effect/platform-browser")).pipe(
|
|
|
|
|
|
|
|
Effect.flatMap(({ BrowserStream }) => this.refreshOnWindowFocus
|
|
|
|
|
|
|
|
? Stream.runForEach(
|
|
|
|
|
|
|
|
BrowserStream.fromEventListenerWindow("focus"),
|
|
|
|
|
|
|
|
() => this.refreshSubscribable,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
: Effect.void
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
Effect.catchDefect(() => Effect.void),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
], { concurrency: "unbounded" }).pipe(
|
|
|
|
Effect.ignore,
|
|
|
|
Effect.ignore,
|
|
|
|
this.runSemaphore.withPermits(1),
|
|
|
|
this.runSemaphore.withPermits(1),
|
|
|
|
Effect.provide(this.context),
|
|
|
|
Effect.provide(this.context),
|
|
|
@@ -90,120 +82,159 @@ extends Pipeable.Class implements Query<K, A, KE, KR, E, R, P> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get interrupt(): Effect.Effect<void> {
|
|
|
|
get interrupt(): Effect.Effect<void> {
|
|
|
|
return Effect.flatMap(SubscriptionRef.get(this.fiberRef), Option.match({
|
|
|
|
return Effect.flatMap(Lens.get(this.fiber), Option.match({
|
|
|
|
onSome: Fiber.interrupt,
|
|
|
|
onSome: Fiber.interrupt,
|
|
|
|
onNone: () => Effect.succeed(undefined),
|
|
|
|
onNone: () => Effect.void,
|
|
|
|
}))
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fetch(key: K): Effect.Effect<Result.Final<A, E, P>> {
|
|
|
|
fetch(key: K): Effect.Effect<
|
|
|
|
const self = this
|
|
|
|
AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>,
|
|
|
|
return Effect.gen(function*() {
|
|
|
|
Cause.NoSuchElementError
|
|
|
|
yield* self.interrupt
|
|
|
|
> {
|
|
|
|
yield* SubscriptionRef.set(self.latestKeyRef, Option.some(key))
|
|
|
|
return this.interrupt.pipe(
|
|
|
|
const previous = yield* SubscriptionRef.get(self.latestFinalResultRef)
|
|
|
|
Effect.andThen(Lens.set(this.latestKey, Option.some(key))),
|
|
|
|
const sub = yield* self.startCached(key, Option.isSome(previous)
|
|
|
|
Effect.andThen(this.startCached(key, AsyncResult.initial())),
|
|
|
|
? Result.willFetch(previous.value) as Result.Final<A, E, P>
|
|
|
|
Effect.flatMap(state => this.watch(key, state)),
|
|
|
|
: Result.initial())
|
|
|
|
Effect.provide(this.context),
|
|
|
|
return yield* self.watch(key, sub)
|
|
|
|
)
|
|
|
|
}).pipe(Effect.provide(this.context))
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fetchSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>> {
|
|
|
|
fetchSubscribable(key: K): Effect.Effect<
|
|
|
|
const self = this
|
|
|
|
Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>>,
|
|
|
|
return Effect.gen(function*() {
|
|
|
|
Cause.NoSuchElementError
|
|
|
|
yield* self.interrupt
|
|
|
|
> {
|
|
|
|
yield* SubscriptionRef.set(self.latestKeyRef, Option.some(key))
|
|
|
|
return this.interrupt.pipe(
|
|
|
|
const previous = yield* SubscriptionRef.get(self.latestFinalResultRef)
|
|
|
|
Effect.andThen(Lens.set(this.latestKey, Option.some(key))),
|
|
|
|
const sub = yield* self.startCached(key, Option.isSome(previous)
|
|
|
|
Effect.andThen(this.startCached(key, AsyncResult.initial(false))),
|
|
|
|
? Result.willFetch(previous.value) as Result.Final<A, E, P>
|
|
|
|
Effect.tap(state => Effect.forkScoped(this.watch(key, state))),
|
|
|
|
: Result.initial())
|
|
|
|
Effect.provide(this.context),
|
|
|
|
yield* Effect.forkScoped(self.watch(key, sub))
|
|
|
|
)
|
|
|
|
return sub
|
|
|
|
|
|
|
|
}).pipe(Effect.provide(this.context))
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get refresh(): Effect.Effect<Result.Final<A, E, P>, Cause.NoSuchElementError> {
|
|
|
|
get refresh(): Effect.Effect<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>, Cause.NoSuchElementError> {
|
|
|
|
const self = this
|
|
|
|
return this.interrupt.pipe(
|
|
|
|
return Effect.gen(function*() {
|
|
|
|
Effect.andThen(Effect.Do),
|
|
|
|
yield* self.interrupt
|
|
|
|
Effect.bind("latestKey", () => Effect.flatMap(Lens.get(this.latestKey), Effect.fromOption)),
|
|
|
|
const key = yield* Effect.fromOption(yield* SubscriptionRef.get(self.latestKeyRef))
|
|
|
|
Effect.bind("latestFinalResult", () => Lens.get(this.latestFinalResult)),
|
|
|
|
const previous = yield* SubscriptionRef.get(self.latestFinalResultRef)
|
|
|
|
Effect.bind("subscribable", ({ latestKey, latestFinalResult }) =>
|
|
|
|
const sub = yield* self.startCached(key, Option.isSome(previous)
|
|
|
|
this.startCached(latestKey, Option.getOrElse(latestFinalResult, () => AsyncResult.initial(true)))
|
|
|
|
? Result.willRefresh(previous.value) as Result.Final<A, E, P>
|
|
|
|
),
|
|
|
|
: Result.initial())
|
|
|
|
Effect.flatMap(({ latestKey, subscribable }) => this.watch(latestKey, subscribable)),
|
|
|
|
return yield* self.watch(key, sub)
|
|
|
|
Effect.provide(this.context),
|
|
|
|
}).pipe(Effect.provide(this.context))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get refreshSubscribable(): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>, Cause.NoSuchElementError> {
|
|
|
|
get refreshSubscribable(): Effect.Effect<
|
|
|
|
const self = this
|
|
|
|
Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>>,
|
|
|
|
return Effect.gen(function*() {
|
|
|
|
Cause.NoSuchElementError
|
|
|
|
yield* self.interrupt
|
|
|
|
> {
|
|
|
|
const key = yield* Effect.fromOption(yield* SubscriptionRef.get(self.latestKeyRef))
|
|
|
|
return this.interrupt.pipe(
|
|
|
|
const previous = yield* SubscriptionRef.get(self.latestFinalResultRef)
|
|
|
|
Effect.andThen(Effect.Do),
|
|
|
|
const sub = yield* self.startCached(key, Option.isSome(previous)
|
|
|
|
Effect.bind("latestKey", () => Effect.flatMap(Lens.get(this.latestKey), Effect.fromOption)),
|
|
|
|
? Result.willRefresh(previous.value) as Result.Final<A, E, P>
|
|
|
|
Effect.bind("latestFinalResult", () => Lens.get(this.latestFinalResult)),
|
|
|
|
: Result.initial())
|
|
|
|
Effect.bind("subscribable", ({ latestKey, latestFinalResult }) =>
|
|
|
|
yield* Effect.forkScoped(self.watch(key, sub))
|
|
|
|
this.startCached(latestKey, Option.getOrElse(latestFinalResult, () => AsyncResult.initial(true)))
|
|
|
|
return sub
|
|
|
|
),
|
|
|
|
}).pipe(Effect.provide(this.context))
|
|
|
|
Effect.tap(({ latestKey, subscribable }) => Effect.forkScoped(this.watch(latestKey, subscribable))),
|
|
|
|
|
|
|
|
Effect.map(({ subscribable }) => subscribable),
|
|
|
|
|
|
|
|
Effect.provide(this.context),
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
startCached(
|
|
|
|
startCached(
|
|
|
|
key: K,
|
|
|
|
key: K,
|
|
|
|
initial: Result.Initial | Result.Final<A, E, P>,
|
|
|
|
previous: AsyncResult.AsyncResult<A, E>,
|
|
|
|
): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>, never, Scope.Scope | QueryClient.QueryClient | R> {
|
|
|
|
): Effect.Effect<
|
|
|
|
|
|
|
|
Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>>,
|
|
|
|
|
|
|
|
Cause.NoSuchElementError,
|
|
|
|
|
|
|
|
Scope.Scope | QueryClient.QueryClient | R
|
|
|
|
|
|
|
|
> {
|
|
|
|
return Effect.flatMap(this.getCacheEntry(key), Option.match({
|
|
|
|
return Effect.flatMap(this.getCacheEntry(key), Option.match({
|
|
|
|
onSome: entry => Effect.flatMap(
|
|
|
|
onSome: entry => Effect.flatMap(
|
|
|
|
QueryClient.isQueryClientCacheEntryStale(entry),
|
|
|
|
QueryClient.isQueryClientCacheEntryStale(entry),
|
|
|
|
isStale => isStale
|
|
|
|
isStale => isStale
|
|
|
|
? this.start(key, Result.willRefresh(entry.result) as Result.Final<A, E, P>)
|
|
|
|
? this.start(key, entry.result as AsyncResult.AsyncResult<A, E>)
|
|
|
|
: Effect.succeed(Subscribable.make({
|
|
|
|
: Effect.succeed(Subscribable.make({
|
|
|
|
get: Effect.succeed(entry.result as Result.Result<A, E, P>),
|
|
|
|
get: Effect.succeed(entry.result as AsyncResult.AsyncResult<A, E>),
|
|
|
|
changes: Stream.make(entry.result as Result.Result<A, E, P>),
|
|
|
|
get changes() { return Stream.make(entry.result as AsyncResult.AsyncResult<A, E>) },
|
|
|
|
})),
|
|
|
|
})),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
onNone: () => this.start(key, initial),
|
|
|
|
onNone: () => this.start(key, previous),
|
|
|
|
}))
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
start(
|
|
|
|
start(
|
|
|
|
key: K,
|
|
|
|
key: K,
|
|
|
|
initial: Result.Initial | Result.Final<A, E, P>,
|
|
|
|
previous: AsyncResult.AsyncResult<A, E>,
|
|
|
|
): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>, never, Scope.Scope | R> {
|
|
|
|
): Effect.Effect<
|
|
|
|
const self = this
|
|
|
|
Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>>,
|
|
|
|
return Effect.gen(function*() {
|
|
|
|
never,
|
|
|
|
const [sub, fiber] = yield* Result.unsafeForkEffect<A, E, R, P>(
|
|
|
|
Scope.Scope | R
|
|
|
|
Effect.onExit(self.f(key), () => Effect.flatMap(
|
|
|
|
> {
|
|
|
|
Effect.all([Effect.fiberId, SubscriptionRef.get(self.fiberRef)]),
|
|
|
|
return Effect.gen({ self: this }, function*() {
|
|
|
|
([currentFiberId, current]) => Option.match(current, {
|
|
|
|
const state = Lens.fromSubscriptionRef(yield* SubscriptionRef.make(previous))
|
|
|
|
onSome: value => Equal.equals(currentFiberId, value.id)
|
|
|
|
|
|
|
|
? SubscriptionRef.set(self.fiberRef, Option.none())
|
|
|
|
const fiber = yield* Effect.forkScoped(Effect.andThen(
|
|
|
|
: Effect.succeed(undefined),
|
|
|
|
Lens.update(state, AsyncResult.match({
|
|
|
|
onNone: () => Effect.succeed(undefined),
|
|
|
|
onInitial: () => AsyncResult.initial(true),
|
|
|
|
|
|
|
|
onSuccess: v => AsyncResult.success(v.value, {
|
|
|
|
|
|
|
|
waiting: true,
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
|
|
|
|
onFailure: v => AsyncResult.failure(v.cause, {
|
|
|
|
|
|
|
|
waiting: true,
|
|
|
|
|
|
|
|
previousSuccess: v.previousSuccess,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
})),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Effect.onExit(this.f(key), exit => Lens.update(
|
|
|
|
|
|
|
|
state,
|
|
|
|
|
|
|
|
previous => Exit.match(exit, {
|
|
|
|
|
|
|
|
onSuccess: v => AsyncResult.success(v),
|
|
|
|
|
|
|
|
onFailure: c => AsyncResult.match(previous, {
|
|
|
|
|
|
|
|
onInitial: () => AsyncResult.failure(c),
|
|
|
|
|
|
|
|
onSuccess: v => AsyncResult.failure(c, {
|
|
|
|
|
|
|
|
previousSuccess: Option.some(v),
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
onFailure: v => AsyncResult.failure(c, {
|
|
|
|
|
|
|
|
previousSuccess: v.previousSuccess,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
).pipe(
|
|
|
|
|
|
|
|
Effect.andThen(Effect.all([
|
|
|
|
|
|
|
|
Effect.fiberId,
|
|
|
|
|
|
|
|
Lens.get(this.fiber),
|
|
|
|
|
|
|
|
])),
|
|
|
|
|
|
|
|
Effect.flatMap(([fiberId, fiber]) => Option.match(fiber, {
|
|
|
|
|
|
|
|
onSome: v => Equal.equals(fiberId, v.id)
|
|
|
|
|
|
|
|
? Lens.set(this.fiber, Option.none())
|
|
|
|
|
|
|
|
: Effect.void,
|
|
|
|
|
|
|
|
onNone: () => Effect.void,
|
|
|
|
|
|
|
|
})),
|
|
|
|
)),
|
|
|
|
)),
|
|
|
|
{ initial, initialProgress: self.initialProgress },
|
|
|
|
))
|
|
|
|
)
|
|
|
|
|
|
|
|
yield* SubscriptionRef.set(self.fiberRef, Option.some(fiber))
|
|
|
|
yield* Lens.set(this.fiber, Option.some(fiber))
|
|
|
|
return sub
|
|
|
|
return state
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
watch(
|
|
|
|
key: K,
|
|
|
|
key: K,
|
|
|
|
sub: Subscribable.Subscribable<Result.Result<A, E, P>>,
|
|
|
|
state: Subscribable.Subscribable<AsyncResult.AsyncResult<A, E>>
|
|
|
|
): Effect.Effect<Result.Final<A, E, P>, never, QueryClient.QueryClient> {
|
|
|
|
): Effect.Effect<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>, never, QueryClient.QueryClient> {
|
|
|
|
return Effect.flatMap(sub.get, initial => Stream.runFoldEffect(
|
|
|
|
return state.get.pipe(
|
|
|
|
Stream.takeUntil(sub.changes, result => Result.isFinal(result) && !Result.hasFlag(result)),
|
|
|
|
Effect.flatMap(initial => Stream.runFoldEffect(
|
|
|
|
|
|
|
|
state.changes,
|
|
|
|
() => initial,
|
|
|
|
() => initial,
|
|
|
|
(_, result) => Effect.as(SubscriptionRef.set(this.resultRef, result), result),
|
|
|
|
(_, result) => Effect.as(Lens.set(this.result, result), result),
|
|
|
|
) as Effect.Effect<Result.Final<A, E, P>>).pipe(
|
|
|
|
) as Effect.Effect<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>>),
|
|
|
|
Effect.tap(result => SubscriptionRef.set(this.latestFinalResultRef, Option.some(result))),
|
|
|
|
Effect.tap(result => Lens.set(this.latestFinalResult, Option.some(result))),
|
|
|
|
Effect.tap(result => Result.isSuccess(result)
|
|
|
|
Effect.tap(result => AsyncResult.isSuccess(result)
|
|
|
|
? Effect.asVoid(this.setCacheEntry(key, result))
|
|
|
|
? this.setCacheEntry(key, result)
|
|
|
|
: Effect.succeed(undefined)),
|
|
|
|
: Effect.void
|
|
|
|
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -211,73 +242,95 @@ extends Pipeable.Class implements Query<K, A, KE, KR, E, R, P> {
|
|
|
|
return new QueryClient.QueryClientCacheKey(key, this.f as (key: Query.AnyKey) => Effect.Effect<unknown, unknown, unknown>)
|
|
|
|
return new QueryClient.QueryClientCacheKey(key, this.f as (key: Query.AnyKey) => Effect.Effect<unknown, unknown, unknown>)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getCacheEntry(key: K): Effect.Effect<Option.Option<QueryClient.QueryClientCacheEntry>, never, QueryClient.QueryClient> {
|
|
|
|
getCacheEntry(
|
|
|
|
return Effect.flatMap(QueryClient.QueryClient, client => client.getCacheEntry(this.makeCacheKey(key)))
|
|
|
|
key: K
|
|
|
|
|
|
|
|
): Effect.Effect<Option.Option<QueryClient.QueryClientCacheEntry>, never, QueryClient.QueryClient> {
|
|
|
|
|
|
|
|
return Effect.andThen(
|
|
|
|
|
|
|
|
Effect.all([
|
|
|
|
|
|
|
|
Effect.succeed(this.makeCacheKey(key)),
|
|
|
|
|
|
|
|
QueryClient.QueryClient,
|
|
|
|
|
|
|
|
]),
|
|
|
|
|
|
|
|
([key, client]) => client.getCacheEntry(key),
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setCacheEntry(key: K, result: Result.Success<A>): Effect.Effect<QueryClient.QueryClientCacheEntry, never, QueryClient.QueryClient> {
|
|
|
|
setCacheEntry(
|
|
|
|
return Effect.flatMap(QueryClient.QueryClient, client => client.setCacheEntry(this.makeCacheKey(key), result, this.staleTime))
|
|
|
|
key: K,
|
|
|
|
|
|
|
|
result: AsyncResult.Success<A, E>,
|
|
|
|
|
|
|
|
): Effect.Effect<QueryClient.QueryClientCacheEntry, never, QueryClient.QueryClient> {
|
|
|
|
|
|
|
|
return Effect.flatMap(
|
|
|
|
|
|
|
|
Effect.all([
|
|
|
|
|
|
|
|
Effect.succeed(this.makeCacheKey(key)),
|
|
|
|
|
|
|
|
QueryClient.QueryClient,
|
|
|
|
|
|
|
|
]),
|
|
|
|
|
|
|
|
([key, client]) => client.setCacheEntry(key, result, this.staleTime),
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get invalidateCache(): Effect.Effect<void> {
|
|
|
|
get invalidateCache(): Effect.Effect<void> {
|
|
|
|
return Effect.flatMap(
|
|
|
|
return QueryClient.QueryClient.pipe(
|
|
|
|
QueryClient.QueryClient,
|
|
|
|
Effect.andThen(client => client.invalidateCacheEntries(this.f as (key: Query.AnyKey) => Effect.Effect<unknown, unknown, unknown>)),
|
|
|
|
client => client.invalidateCacheEntries(this.f as (key: Query.AnyKey) => Effect.Effect<unknown, unknown, unknown>),
|
|
|
|
Effect.provide(this.context),
|
|
|
|
).pipe(Effect.provide(this.context))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
invalidateCacheEntry(key: K): Effect.Effect<void> {
|
|
|
|
invalidateCacheEntry(key: K): Effect.Effect<void> {
|
|
|
|
return Effect.flatMap(
|
|
|
|
return Effect.all([
|
|
|
|
|
|
|
|
Effect.succeed(this.makeCacheKey(key)),
|
|
|
|
QueryClient.QueryClient,
|
|
|
|
QueryClient.QueryClient,
|
|
|
|
client => client.invalidateCacheEntry(this.makeCacheKey(key)),
|
|
|
|
]).pipe(
|
|
|
|
).pipe(Effect.provide(this.context))
|
|
|
|
Effect.andThen(([key, client]) => client.invalidateCacheEntry(key)),
|
|
|
|
|
|
|
|
Effect.provide(this.context),
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const isQuery = (u: unknown): u is Query<readonly unknown[], unknown> => Predicate.hasProperty(u, QueryTypeId)
|
|
|
|
export const isQuery = (u: unknown): u is Query<readonly unknown[], unknown> => Predicate.hasProperty(u, QueryTypeId)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export declare namespace make {
|
|
|
|
export declare namespace make {
|
|
|
|
export interface Options<K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never, P = never> {
|
|
|
|
export interface Options<K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never> {
|
|
|
|
readonly key: Stream.Stream<K, KE, KR>
|
|
|
|
readonly key: Stream.Stream<K, KE, KR>
|
|
|
|
readonly f: (key: NoInfer<K>) => Effect.Effect<A, E, Result.forkEffect.InputContext<R, NoInfer<P>>>
|
|
|
|
readonly f: (key: NoInfer<K>) => Effect.Effect<A, E, R>
|
|
|
|
readonly initialProgress?: P
|
|
|
|
|
|
|
|
readonly staleTime?: Duration.Input
|
|
|
|
readonly staleTime?: Duration.Input
|
|
|
|
readonly refreshOnWindowFocus?: boolean
|
|
|
|
readonly refreshOnWindowFocus?: boolean
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const make = Effect.fnUntraced(function* <K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never, P = never>(
|
|
|
|
export const make = Effect.fnUntraced(function* <K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never>(
|
|
|
|
options: make.Options<K, A, KE, KR, E, R, P>,
|
|
|
|
options: make.Options<K, A, KE, KR, E, R>
|
|
|
|
): Effect.fn.Return<
|
|
|
|
): Effect.fn.Return<
|
|
|
|
Query<K, A, KE, KR, E, Result.forkEffect.OutputContext<R, P>, P>,
|
|
|
|
Query<K, A, KE, KR, E, R>,
|
|
|
|
never,
|
|
|
|
Cause.NoSuchElementError,
|
|
|
|
Scope.Scope | QueryClient.QueryClient | KR | Result.forkEffect.OutputContext<R, P>
|
|
|
|
Scope.Scope | QueryClient.QueryClient | KR | R
|
|
|
|
> {
|
|
|
|
> {
|
|
|
|
const client = yield* QueryClient.QueryClient
|
|
|
|
const client = yield* QueryClient.QueryClient
|
|
|
|
return new QueryImpl<K, A, KE, KR, E, Result.forkEffect.OutputContext<R, P>, P>(
|
|
|
|
|
|
|
|
yield* Effect.context<Scope.Scope | QueryClient.QueryClient | KR | Result.forkEffect.OutputContext<R, P>>(),
|
|
|
|
return new QueryImpl(
|
|
|
|
|
|
|
|
yield* Effect.context<Scope.Scope | QueryClient.QueryClient | KR | R>(),
|
|
|
|
options.key,
|
|
|
|
options.key,
|
|
|
|
options.f as any,
|
|
|
|
options.f,
|
|
|
|
options.initialProgress as P,
|
|
|
|
|
|
|
|
options.staleTime ?? client.defaultStaleTime,
|
|
|
|
options.staleTime ? yield* Effect.fromOption(Duration.fromInput(options.staleTime)) : client.defaultStaleTime,
|
|
|
|
options.refreshOnWindowFocus ?? client.defaultRefreshOnWindowFocus,
|
|
|
|
options.refreshOnWindowFocus ?? client.defaultRefreshOnWindowFocus,
|
|
|
|
yield* SubscriptionRef.make(Option.none<K>()),
|
|
|
|
|
|
|
|
yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, E>>()),
|
|
|
|
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<K>())),
|
|
|
|
yield* SubscriptionRef.make(Result.initial<A, E, P>()),
|
|
|
|
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, E>>())),
|
|
|
|
yield* SubscriptionRef.make(Option.none<Result.Final<A, E, P>>()),
|
|
|
|
Lens.fromSubscriptionRef(yield* SubscriptionRef.make<AsyncResult.AsyncResult<A, E>>(AsyncResult.initial())),
|
|
|
|
|
|
|
|
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<AsyncResult.Success<A, E> | AsyncResult.Failure<A, E>>())),
|
|
|
|
|
|
|
|
|
|
|
|
yield* Semaphore.make(1),
|
|
|
|
yield* Semaphore.make(1),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const service = <K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never, P = never>(
|
|
|
|
export const service = <K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never>(
|
|
|
|
options: make.Options<K, A, KE, KR, E, R, P>,
|
|
|
|
options: make.Options<K, A, KE, KR, E, R>
|
|
|
|
): Effect.Effect<
|
|
|
|
): Effect.Effect<
|
|
|
|
Query<K, A, KE, KR, E, Result.forkEffect.OutputContext<R, P>, P>,
|
|
|
|
Query<K, A, KE, KR, E, R>,
|
|
|
|
never,
|
|
|
|
Cause.NoSuchElementError,
|
|
|
|
Scope.Scope | QueryClient.QueryClient | KR | Result.forkEffect.OutputContext<R, P>
|
|
|
|
Scope.Scope | QueryClient.QueryClient | KR | R
|
|
|
|
> => Effect.tap(make(options), query => Effect.asVoid(Effect.forkScoped(query.run)))
|
|
|
|
> => Effect.tap(
|
|
|
|
|
|
|
|
make(options),
|
|
|
|
const fromSubscriptionRef = <A>(ref: SubscriptionRef.SubscriptionRef<A>): Subscribable.Subscribable<A> => Subscribable.make({
|
|
|
|
query => Effect.forkScoped(query.run),
|
|
|
|
get: SubscriptionRef.get(ref),
|
|
|
|
)
|
|
|
|
changes: SubscriptionRef.changes(ref),
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|