Compare commits
8 Commits
renovate/b
...
c07f63c5a2
| Author | SHA1 | Date | |
|---|---|---|---|
| c07f63c5a2 | |||
|
|
49c79295d6 | ||
|
|
929e062d0c | ||
|
|
c9dd4e6aa9 | ||
|
|
1779eebe3b | ||
|
|
f88daeefd4 | ||
|
|
bbacee7ad4 | ||
|
|
636beedd13 |
4
bun.lock
4
bun.lock
@@ -6,7 +6,7 @@
|
|||||||
"name": "@effect-fc/monorepo",
|
"name": "@effect-fc/monorepo",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.3.8",
|
"@biomejs/biome": "^2.3.8",
|
||||||
"@effect/language-service": "^0.65.0",
|
"@effect/language-service": "^0.71.0",
|
||||||
"@types/bun": "^1.3.3",
|
"@types/bun": "^1.3.3",
|
||||||
"npm-check-updates": "^19.1.2",
|
"npm-check-updates": "^19.1.2",
|
||||||
"npm-sort": "^0.0.4",
|
"npm-sort": "^0.0.4",
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
|
|
||||||
"@effect-fc/example": ["@effect-fc/example@workspace:packages/example"],
|
"@effect-fc/example": ["@effect-fc/example@workspace:packages/example"],
|
||||||
|
|
||||||
"@effect/language-service": ["@effect/language-service@0.65.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-eHcpLNCZa1XEDRrXLZqTdky6jAQojL6zQEW53Ba6vJL35j77tJTnV9BFkk34G3rxKoplNo39U0Mum3RfuH9rsg=="],
|
"@effect/language-service": ["@effect/language-service@0.71.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-ttfev0+RFba5j4/xp+6puRNK7qDBxduT37zuKtNN4ylZkIw2MW5eS4cCgLAXrz/T2K3gdliJ3AP/yiNYPVoOpg=="],
|
||||||
|
|
||||||
"@effect/platform": ["@effect/platform@0.94.1", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.14" } }, "sha512-SlL8OMTogHmMNnFLnPAHHo3ua1yrB1LNQOVQMiZsqYu9g3216xjr0gn5WoDgCxUyOdZcseegMjWJ7dhm/2vnfg=="],
|
"@effect/platform": ["@effect/platform@0.94.1", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.14" } }, "sha512-SlL8OMTogHmMNnFLnPAHHo3ua1yrB1LNQOVQMiZsqYu9g3216xjr0gn5WoDgCxUyOdZcseegMjWJ7dhm/2vnfg=="],
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.3.8",
|
"@biomejs/biome": "^2.3.8",
|
||||||
"@effect/language-service": "^0.65.0",
|
"@effect/language-service": "^0.71.0",
|
||||||
"@types/bun": "^1.3.3",
|
"@types/bun": "^1.3.3",
|
||||||
"npm-check-updates": "^19.1.2",
|
"npm-check-updates": "^19.1.2",
|
||||||
"npm-sort": "^0.0.4",
|
"npm-sort": "^0.0.4",
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ import { Memoized } from "./index.js"
|
|||||||
export const TypeId: unique symbol = Symbol.for("@effect-fc/Component/Component")
|
export const TypeId: unique symbol = Symbol.for("@effect-fc/Component/Component")
|
||||||
export type TypeId = typeof TypeId
|
export type TypeId = typeof TypeId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface representing an Effect-based React Component.
|
||||||
|
*
|
||||||
|
* This is both:
|
||||||
|
* - an Effect that produces a React function component
|
||||||
|
* - a constructor-like object with component metadata and options
|
||||||
|
*/
|
||||||
export interface Component<P extends {}, A extends React.ReactNode, E, R>
|
export interface Component<P extends {}, A extends React.ReactNode, E, R>
|
||||||
extends
|
extends
|
||||||
Effect.Effect<(props: P) => A, never, Exclude<R, Scope.Scope>>,
|
Effect.Effect<(props: P) => A, never, Exclude<R, Scope.Scope>>,
|
||||||
@@ -20,7 +27,6 @@ extends
|
|||||||
readonly "~Error": E
|
readonly "~Error": E
|
||||||
readonly "~Context": R
|
readonly "~Context": R
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
readonly body: (props: P) => Effect.Effect<A, E, R>
|
readonly body: (props: P) => Effect.Effect<A, E, R>
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
@@ -37,9 +43,22 @@ export declare namespace Component {
|
|||||||
|
|
||||||
export type AsComponent<T extends Component<any, any, any, any>> = Component<Props<T>, Success<T>, Error<T>, Context<T>>
|
export type AsComponent<T extends Component<any, any, any, any>> = Component<Props<T>, Success<T>, Error<T>, Context<T>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options that can be set on the component
|
||||||
|
*/
|
||||||
export interface Options {
|
export interface Options {
|
||||||
|
/** Custom displayName for React DevTools and debugging */
|
||||||
readonly displayName?: string
|
readonly displayName?: string
|
||||||
|
/**
|
||||||
|
* Strategy used when executing finalizers on unmount/scope close
|
||||||
|
* @default ExecutionStrategy.sequential
|
||||||
|
*/
|
||||||
readonly finalizerExecutionStrategy: ExecutionStrategy.ExecutionStrategy
|
readonly finalizerExecutionStrategy: ExecutionStrategy.ExecutionStrategy
|
||||||
|
/**
|
||||||
|
* Debounce time before executing finalizers after component unmount
|
||||||
|
* Helps avoid unnecessary work during fast remount/remount cycles
|
||||||
|
* @default "100 millis"
|
||||||
|
*/
|
||||||
readonly finalizerExecutionDebounce: Duration.DurationInput
|
readonly finalizerExecutionDebounce: Duration.DurationInput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,6 +337,11 @@ export declare namespace make {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an Effect-based React component.
|
||||||
|
*
|
||||||
|
* Follows the `Effect.fn` API. Supports both generator syntax (recommended) and direct Effect composition.
|
||||||
|
*/
|
||||||
export const make: (
|
export const make: (
|
||||||
& make.Gen
|
& make.Gen
|
||||||
& make.NonGen
|
& make.NonGen
|
||||||
@@ -346,6 +370,12 @@ export const make: (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as `make` but creates an untraced version (no automatic span created).
|
||||||
|
* Useful for very low-level utilities or when you want full control over tracing.
|
||||||
|
*
|
||||||
|
* Follows the `Effect.fnUntraced` API.
|
||||||
|
*/
|
||||||
export const makeUntraced: (
|
export const makeUntraced: (
|
||||||
& make.Gen
|
& make.Gen
|
||||||
& make.NonGen
|
& make.NonGen
|
||||||
@@ -367,6 +397,9 @@ export const makeUntraced: (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new component with modified options while preserving original behavior.
|
||||||
|
*/
|
||||||
export const withOptions: {
|
export const withOptions: {
|
||||||
<T extends Component<any, any, any, any>>(
|
<T extends Component<any, any, any, any>>(
|
||||||
options: Partial<Component.Options>
|
options: Partial<Component.Options>
|
||||||
@@ -383,6 +416,39 @@ export const withOptions: {
|
|||||||
Object.getPrototypeOf(self),
|
Object.getPrototypeOf(self),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an Effect-FC `Component` and turns it into a regular React function component
|
||||||
|
* that serves as an **entrypoint** into an Effect-FC component hierarchy.
|
||||||
|
*
|
||||||
|
* This is the recommended way to connect Effect-FC components to the rest of your React app,
|
||||||
|
* especially when using routers (TanStack Router, React Router, etc.), lazy-loaded routes,
|
||||||
|
* or any place where a standard React component is expected.
|
||||||
|
*
|
||||||
|
* The runtime is obtained from the provided React Context, allowing you to:
|
||||||
|
* - Provide dependencies once at a high level
|
||||||
|
* - Use the same runtime across an entire route tree or feature
|
||||||
|
*
|
||||||
|
* @example Using TanStack Router
|
||||||
|
* ```tsx
|
||||||
|
* // Main
|
||||||
|
* export const runtime = ReactRuntime.make(Layer.empty)
|
||||||
|
* function App() {
|
||||||
|
* return (
|
||||||
|
* <ReactRuntime.Provider runtime={runtime}>
|
||||||
|
* <RouterProvider router={router} />
|
||||||
|
* </ReactRuntime.Provider>
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Route
|
||||||
|
* export const Route = createFileRoute("/")({
|
||||||
|
* component: withRuntime(HomePage, runtime.context),
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param self - The Effect-FC Component you want to render as a regular React component.
|
||||||
|
* @param context - React Context that holds the Runtime to use for this component tree. See the `ReactRuntime` module to create one.
|
||||||
|
*/
|
||||||
export const withRuntime: {
|
export const withRuntime: {
|
||||||
<P extends {}, A extends React.ReactNode, E, R>(
|
<P extends {}, A extends React.ReactNode, E, R>(
|
||||||
context: React.Context<Runtime.Runtime<R>>,
|
context: React.Context<Runtime.Runtime<R>>,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type Cause, type Context, DateTime, type Duration, Effect, Equal, Equivalence, Fiber, HashMap, identity, Option, Pipeable, Predicate, type Scope, Stream, Subscribable, SubscriptionRef } from "effect"
|
import { type Cause, type Context, type Duration, Effect, Equal, Fiber, identity, Option, Pipeable, Predicate, type Scope, Stream, Subscribable, SubscriptionRef } from "effect"
|
||||||
import * as QueryClient from "./QueryClient.js"
|
import * as QueryClient from "./QueryClient.js"
|
||||||
import * as Result from "./Result.js"
|
import * as Result from "./Result.js"
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ extends Pipeable.Class() implements Query<K, A, E, R, P> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get interrupt(): Effect.Effect<void, never, never> {
|
get interrupt(): Effect.Effect<void> {
|
||||||
return Effect.andThen(this.fiber, Option.match({
|
return Effect.andThen(this.fiber, Option.match({
|
||||||
onSome: Fiber.interrupt,
|
onSome: Fiber.interrupt,
|
||||||
onNone: () => Effect.void,
|
onNone: () => Effect.void,
|
||||||
@@ -159,7 +159,7 @@ extends Pipeable.Class() implements Query<K, A, E, R, P> {
|
|||||||
> {
|
> {
|
||||||
return Effect.andThen(this.getCacheEntry(key), Option.match({
|
return Effect.andThen(this.getCacheEntry(key), Option.match({
|
||||||
onSome: entry => Effect.andThen(
|
onSome: entry => Effect.andThen(
|
||||||
QueryClient.isQueryClientCacheEntryStale(entry, this.staleTime),
|
QueryClient.isQueryClientCacheEntryStale(entry),
|
||||||
isStale => isStale
|
isStale => isStale
|
||||||
? this.start(key, Result.willRefresh(entry.result) as Result.Final<A, E, P>)
|
? this.start(key, Result.willRefresh(entry.result) as Result.Final<A, E, P>)
|
||||||
: Effect.succeed(Subscribable.make({
|
: Effect.succeed(Subscribable.make({
|
||||||
@@ -212,7 +212,7 @@ extends Pipeable.Class() implements Query<K, A, E, R, P> {
|
|||||||
) as Effect.Effect<Result.Final<A, E, P>>),
|
) as Effect.Effect<Result.Final<A, E, P>>),
|
||||||
Effect.tap(result => SubscriptionRef.set(this.latestFinalResult, Option.some(result))),
|
Effect.tap(result => SubscriptionRef.set(this.latestFinalResult, Option.some(result))),
|
||||||
Effect.tap(result => Result.isSuccess(result)
|
Effect.tap(result => Result.isSuccess(result)
|
||||||
? this.updateCacheEntry(key, result)
|
? this.setCacheEntry(key, result)
|
||||||
: Effect.void
|
: Effect.void
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -225,44 +225,41 @@ extends Pipeable.Class() implements Query<K, A, E, R, P> {
|
|||||||
getCacheEntry(
|
getCacheEntry(
|
||||||
key: K
|
key: K
|
||||||
): Effect.Effect<Option.Option<QueryClient.QueryClientCacheEntry>, never, QueryClient.QueryClient> {
|
): Effect.Effect<Option.Option<QueryClient.QueryClientCacheEntry>, never, QueryClient.QueryClient> {
|
||||||
return QueryClient.QueryClient.pipe(
|
return Effect.andThen(
|
||||||
Effect.andThen(client => client.cache),
|
Effect.all([
|
||||||
Effect.map(HashMap.get(this.makeCacheKey(key))),
|
Effect.succeed(this.makeCacheKey(key)),
|
||||||
|
QueryClient.QueryClient,
|
||||||
|
]),
|
||||||
|
([key, client]) => client.getCacheEntry(key),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCacheEntry(
|
setCacheEntry(
|
||||||
key: K,
|
key: K,
|
||||||
result: Result.Success<A>,
|
result: Result.Success<A>,
|
||||||
): Effect.Effect<QueryClient.QueryClientCacheEntry, never, QueryClient.QueryClient> {
|
): Effect.Effect<QueryClient.QueryClientCacheEntry, never, QueryClient.QueryClient> {
|
||||||
return Effect.Do.pipe(
|
return Effect.andThen(
|
||||||
Effect.bind("client", () => QueryClient.QueryClient),
|
Effect.all([
|
||||||
Effect.bind("now", () => DateTime.now),
|
Effect.succeed(this.makeCacheKey(key)),
|
||||||
Effect.let("entry", ({ now }) => new QueryClient.QueryClientCacheEntry(result, now)),
|
QueryClient.QueryClient,
|
||||||
Effect.tap(({ client, entry }) => SubscriptionRef.update(
|
]),
|
||||||
client.cache,
|
([key, client]) => client.setCacheEntry(key, result, this.staleTime),
|
||||||
HashMap.set(this.makeCacheKey(key), entry),
|
|
||||||
)),
|
|
||||||
Effect.map(({ entry }) => entry),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get invalidateCache(): Effect.Effect<void> {
|
get invalidateCache(): Effect.Effect<void> {
|
||||||
return QueryClient.QueryClient.pipe(
|
return QueryClient.QueryClient.pipe(
|
||||||
Effect.andThen(client => SubscriptionRef.update(
|
Effect.andThen(client => client.invalidateCacheEntries(this.f as (key: Query.AnyKey) => Effect.Effect<unknown, unknown, unknown>)),
|
||||||
client.cache,
|
|
||||||
HashMap.filter((_, key) => !Equivalence.strict()(key.f, this.f)),
|
|
||||||
)),
|
|
||||||
Effect.provide(this.context),
|
Effect.provide(this.context),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidateCacheEntry(key: K): Effect.Effect<void> {
|
invalidateCacheEntry(key: K): Effect.Effect<void> {
|
||||||
return QueryClient.QueryClient.pipe(
|
return Effect.all([
|
||||||
Effect.andThen(client => SubscriptionRef.update(
|
Effect.succeed(this.makeCacheKey(key)),
|
||||||
client.cache,
|
QueryClient.QueryClient,
|
||||||
HashMap.remove(this.makeCacheKey(key)),
|
]).pipe(
|
||||||
)),
|
Effect.andThen(([key, client]) => client.invalidateCacheEntry(key)),
|
||||||
Effect.provide(this.context),
|
Effect.provide(this.context),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
import { DateTime, Duration, Effect, Equal, Equivalence, Hash, HashMap, Pipeable, Predicate, type Scope, SubscriptionRef } from "effect"
|
import { DateTime, Duration, Effect, Equal, Equivalence, Hash, HashMap, type Option, Pipeable, Predicate, Schedule, type Scope, type Subscribable, SubscriptionRef } from "effect"
|
||||||
import type * as Query from "./Query.js"
|
import type * as Query from "./Query.js"
|
||||||
import type * as Result from "./Result.js"
|
import type * as Result from "./Result.js"
|
||||||
|
|
||||||
|
|
||||||
export const QueryClientServiceTypeId: unique symbol = Symbol.for("@effect-fc/QueryClient/QueryClientServiceTypeId")
|
export const QueryClientServiceTypeId: unique symbol = Symbol.for("@effect-fc/QueryClient/QueryClientService")
|
||||||
export type QueryClientServiceTypeId = typeof QueryClientServiceTypeId
|
export type QueryClientServiceTypeId = typeof QueryClientServiceTypeId
|
||||||
|
|
||||||
export interface QueryClientService extends Pipeable.Pipeable {
|
export interface QueryClientService extends Pipeable.Pipeable {
|
||||||
readonly [QueryClientServiceTypeId]: QueryClientServiceTypeId
|
readonly [QueryClientServiceTypeId]: QueryClientServiceTypeId
|
||||||
readonly cache: SubscriptionRef.SubscriptionRef<HashMap.HashMap<QueryClientCacheKey, QueryClientCacheEntry>>
|
|
||||||
readonly gcTime: Duration.DurationInput
|
readonly cache: Subscribable.Subscribable<HashMap.HashMap<QueryClientCacheKey, QueryClientCacheEntry>>
|
||||||
|
readonly cacheGcTime: Duration.DurationInput
|
||||||
readonly defaultStaleTime: Duration.DurationInput
|
readonly defaultStaleTime: Duration.DurationInput
|
||||||
readonly defaultRefreshOnWindowFocus: boolean
|
readonly defaultRefreshOnWindowFocus: boolean
|
||||||
|
|
||||||
|
readonly run: Effect.Effect<void>
|
||||||
|
getCacheEntry(key: QueryClientCacheKey): Effect.Effect<Option.Option<QueryClientCacheEntry>>
|
||||||
|
setCacheEntry(
|
||||||
|
key: QueryClientCacheKey,
|
||||||
|
result: Result.Success<unknown>,
|
||||||
|
staleTime: Duration.DurationInput,
|
||||||
|
): Effect.Effect<QueryClientCacheEntry>
|
||||||
|
invalidateCacheEntries(f: (key: Query.Query.AnyKey) => Effect.Effect<unknown, unknown, unknown>): Effect.Effect<void>
|
||||||
|
invalidateCacheEntry(key: QueryClientCacheKey): Effect.Effect<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryClient extends Effect.Service<QueryClient>()("@effect-fc/QueryClient/QueryClient", {
|
export class QueryClient extends Effect.Service<QueryClient>()("@effect-fc/QueryClient/QueryClient", {
|
||||||
@@ -25,20 +36,64 @@ implements QueryClientService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly cache: SubscriptionRef.SubscriptionRef<HashMap.HashMap<QueryClientCacheKey, QueryClientCacheEntry>>,
|
readonly cache: SubscriptionRef.SubscriptionRef<HashMap.HashMap<QueryClientCacheKey, QueryClientCacheEntry>>,
|
||||||
readonly gcTime: Duration.DurationInput,
|
readonly cacheGcTime: Duration.DurationInput,
|
||||||
readonly defaultStaleTime: Duration.DurationInput,
|
readonly defaultStaleTime: Duration.DurationInput,
|
||||||
readonly defaultRefreshOnWindowFocus: boolean,
|
readonly defaultRefreshOnWindowFocus: boolean,
|
||||||
readonly runSemaphore: Effect.Semaphore,
|
readonly runSemaphore: Effect.Semaphore,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get run(): Effect.Effect<void> {
|
||||||
|
return this.runSemaphore.withPermits(1)(Effect.repeat(
|
||||||
|
Effect.andThen(
|
||||||
|
DateTime.now,
|
||||||
|
now => SubscriptionRef.update(this.cache, HashMap.filter(entry =>
|
||||||
|
Duration.lessThan(
|
||||||
|
DateTime.distanceDuration(entry.lastAccessedAt, now),
|
||||||
|
Duration.sum(entry.staleTime, this.cacheGcTime),
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Schedule.spaced("30 second"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
getCacheEntry(key: QueryClientCacheKey): Effect.Effect<Option.Option<QueryClientCacheEntry>> {
|
||||||
|
return Effect.all([
|
||||||
|
Effect.andThen(this.cache, HashMap.get(key)),
|
||||||
|
DateTime.now,
|
||||||
|
]).pipe(
|
||||||
|
Effect.map(([entry, now]) => new QueryClientCacheEntry(entry.result, entry.staleTime, entry.createdAt, now)),
|
||||||
|
Effect.tap(entry => SubscriptionRef.update(this.cache, HashMap.set(key, entry))),
|
||||||
|
Effect.option,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setCacheEntry(
|
||||||
|
key: QueryClientCacheKey,
|
||||||
|
result: Result.Success<unknown>,
|
||||||
|
staleTime: Duration.DurationInput,
|
||||||
|
): Effect.Effect<QueryClientCacheEntry> {
|
||||||
|
return DateTime.now.pipe(
|
||||||
|
Effect.map(now => new QueryClientCacheEntry(result, staleTime, now, now)),
|
||||||
|
Effect.tap(entry => SubscriptionRef.update(this.cache, HashMap.set(key, entry))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateCacheEntries(f: (key: Query.Query.AnyKey) => Effect.Effect<unknown, unknown, unknown>): Effect.Effect<void> {
|
||||||
|
return SubscriptionRef.update(this.cache, HashMap.filter((_, key) => !Equivalence.strict()(key.f, f)))
|
||||||
|
}
|
||||||
|
invalidateCacheEntry(key: QueryClientCacheKey): Effect.Effect<void> {
|
||||||
|
return SubscriptionRef.update(this.cache, HashMap.remove(key))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isQueryClientService = (u: unknown): u is QueryClientService => Predicate.hasProperty(u, QueryClientServiceTypeId)
|
export const isQueryClientService = (u: unknown): u is QueryClientService => Predicate.hasProperty(u, QueryClientServiceTypeId)
|
||||||
|
|
||||||
export declare namespace make {
|
export declare namespace make {
|
||||||
export interface Options {
|
export interface Options {
|
||||||
readonly gcTime?: Duration.DurationInput
|
readonly cacheGcTime?: Duration.DurationInput
|
||||||
readonly defaultStaleTime?: Duration.DurationInput
|
readonly defaultStaleTime?: Duration.DurationInput
|
||||||
readonly defaultRefreshOnWindowFocus?: boolean
|
readonly defaultRefreshOnWindowFocus?: boolean
|
||||||
}
|
}
|
||||||
@@ -47,22 +102,20 @@ export declare namespace make {
|
|||||||
export const make = Effect.fnUntraced(function* (options: make.Options = {}): Effect.fn.Return<QueryClientService> {
|
export const make = Effect.fnUntraced(function* (options: make.Options = {}): Effect.fn.Return<QueryClientService> {
|
||||||
return new QueryClientServiceImpl(
|
return new QueryClientServiceImpl(
|
||||||
yield* SubscriptionRef.make(HashMap.empty<QueryClientCacheKey, QueryClientCacheEntry>()),
|
yield* SubscriptionRef.make(HashMap.empty<QueryClientCacheKey, QueryClientCacheEntry>()),
|
||||||
options.gcTime ?? "5 minutes",
|
options.cacheGcTime ?? "5 minutes",
|
||||||
options.defaultStaleTime ?? "0 minutes",
|
options.defaultStaleTime ?? "0 minutes",
|
||||||
options.defaultRefreshOnWindowFocus ?? true,
|
options.defaultRefreshOnWindowFocus ?? true,
|
||||||
yield* Effect.makeSemaphore(1),
|
yield* Effect.makeSemaphore(1),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const run = (_self: QueryClientService): Effect.Effect<void> => Effect.void
|
|
||||||
|
|
||||||
export declare namespace service {
|
export declare namespace service {
|
||||||
export interface Options extends make.Options {}
|
export interface Options extends make.Options {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const service = (options?: service.Options): Effect.Effect<QueryClientService, never, Scope.Scope> => Effect.tap(
|
export const service = (options?: service.Options): Effect.Effect<QueryClientService, never, Scope.Scope> => Effect.tap(
|
||||||
make(options),
|
make(options),
|
||||||
client => Effect.forkScoped(run(client)),
|
client => Effect.forkScoped(client.run),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -102,7 +155,9 @@ implements Pipeable.Pipeable {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly result: Result.Success<unknown>,
|
readonly result: Result.Success<unknown>,
|
||||||
|
readonly staleTime: Duration.DurationInput,
|
||||||
readonly createdAt: DateTime.DateTime,
|
readonly createdAt: DateTime.DateTime,
|
||||||
|
readonly lastAccessedAt: DateTime.DateTime,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
@@ -111,9 +166,8 @@ implements Pipeable.Pipeable {
|
|||||||
export const isQueryClientCacheEntry = (u: unknown): u is QueryClientCacheEntry => Predicate.hasProperty(u, QueryClientCacheEntryTypeId)
|
export const isQueryClientCacheEntry = (u: unknown): u is QueryClientCacheEntry => Predicate.hasProperty(u, QueryClientCacheEntryTypeId)
|
||||||
|
|
||||||
export const isQueryClientCacheEntryStale = (
|
export const isQueryClientCacheEntryStale = (
|
||||||
self: QueryClientCacheEntry,
|
self: QueryClientCacheEntry
|
||||||
staleTime: Duration.DurationInput,
|
|
||||||
): Effect.Effect<boolean> => Effect.andThen(
|
): Effect.Effect<boolean> => Effect.andThen(
|
||||||
DateTime.now,
|
DateTime.now,
|
||||||
now => Duration.greaterThanOrEqualTo(DateTime.distanceDuration(self.createdAt, now), staleTime),
|
now => Duration.greaterThanOrEqualTo(DateTime.distanceDuration(self.createdAt, now), self.staleTime),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user