Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf80af9081 | |||
| e412943e64 |
@@ -40,6 +40,13 @@ jobs:
|
|||||||
access: public
|
access: public
|
||||||
token: ${{ secrets.NPM_TOKEN }}
|
token: ${{ secrets.NPM_TOKEN }}
|
||||||
registry: https://registry.npmjs.org
|
registry: https://registry.npmjs.org
|
||||||
|
- name: Publish @effect-view/vite-plugin
|
||||||
|
uses: JS-DevTools/npm-publish@v4
|
||||||
|
with:
|
||||||
|
package: packages/vite-plugin
|
||||||
|
access: public
|
||||||
|
token: ${{ secrets.NPM_TOKEN }}
|
||||||
|
registry: https://registry.npmjs.org
|
||||||
- name: Publish effect-fc
|
- name: Publish effect-fc
|
||||||
uses: JS-DevTools/npm-publish@v4
|
uses: JS-DevTools/npm-publish@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -248,6 +248,33 @@ workflows that need to wait for the final success or failure state.
|
|||||||
Invalidating does not itself refetch. Follow it with `refreshView`, change the
|
Invalidating does not itself refetch. Follow it with `refreshView`, change the
|
||||||
key, or allow a later fetch to repopulate the cache.
|
key, or allow a later fetch to repopulate the cache.
|
||||||
|
|
||||||
|
### Refresh on an interval
|
||||||
|
|
||||||
|
Refresh every five minutes, starting after five minutes:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Schedule } from "effect"
|
||||||
|
|
||||||
|
yield* query.pipe(
|
||||||
|
Query.withScheduledRefresh(Schedule.spaced("5 minutes")),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Limit the number of refreshes:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
yield* query.pipe(
|
||||||
|
Query.withScheduledRefresh(
|
||||||
|
Schedule.spaced("5 minutes").pipe(
|
||||||
|
Schedule.upTo({ times: 3 }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The refresh fiber stops with the surrounding scope. The Effect returns the
|
||||||
|
original query. Cache and `staleTime` rules still apply.
|
||||||
|
|
||||||
## Staleness and cache lifetime
|
## Staleness and cache lifetime
|
||||||
|
|
||||||
`staleTime` controls how long a successful result can satisfy a fetch without
|
`staleTime` controls how long a successful result can satisfy a fetch without
|
||||||
|
|||||||
@@ -41,8 +41,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@effect/platform-browser": "^0.76.0",
|
"@effect/platform-browser": "^0.76.0",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"jsdom": "^30.0.0",
|
"jsdom": "^26.1.0",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^4.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.2.0",
|
"@types/react": "^19.2.0",
|
||||||
|
|||||||
@@ -100,8 +100,8 @@
|
|||||||
"@effect/platform-browser": "4.0.0-beta.101",
|
"@effect/platform-browser": "4.0.0-beta.101",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"effect": "4.0.0-beta.101",
|
"effect": "4.0.0-beta.101",
|
||||||
"jsdom": "^30.0.0",
|
"jsdom": "^26.1.0",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^4.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.2.0",
|
"@types/react": "^19.2.0",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Effect, type Scope, Stream } from "effect"
|
import { Effect, Schedule, type Scope, Stream } from "effect"
|
||||||
|
import { TestClock } from "effect/testing"
|
||||||
import { AsyncResult } from "effect/unstable/reactivity"
|
import { AsyncResult } from "effect/unstable/reactivity"
|
||||||
import { describe, expect, it } from "vitest"
|
import { describe, expect, it } from "vitest"
|
||||||
import * as Query from "./Query.js"
|
import * as Query from "./Query.js"
|
||||||
@@ -79,6 +80,49 @@ describe("Query", () => {
|
|||||||
expect(expectSuccessValue(result[1])).toBe("value:1:2")
|
expect(expectSuccessValue(result[1])).toBe("value:1:2")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("withScheduledRefresh lets the Schedule control the first refresh", async () => {
|
||||||
|
let calls = 0
|
||||||
|
const key = staticKey<readonly [number]>([1])
|
||||||
|
|
||||||
|
const result = await runQueryTest(Effect.gen(function*() {
|
||||||
|
const query = yield* Query.make({
|
||||||
|
key,
|
||||||
|
f: () => Effect.sync(() => {
|
||||||
|
calls += 1
|
||||||
|
return calls
|
||||||
|
}),
|
||||||
|
staleTime: "0 millis",
|
||||||
|
})
|
||||||
|
|
||||||
|
yield* query.fetch([1])
|
||||||
|
const returnedQuery = yield* query.pipe(Query.withScheduledRefresh(
|
||||||
|
Schedule.spaced("1 second").pipe(
|
||||||
|
Schedule.upTo({ times: 1 }),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
yield* TestClock.adjust("999 millis")
|
||||||
|
const beforeInterval = calls
|
||||||
|
|
||||||
|
yield* TestClock.adjust("1 millis")
|
||||||
|
const afterFirstInterval = calls
|
||||||
|
|
||||||
|
return {
|
||||||
|
returnsQuery: returnedQuery === query,
|
||||||
|
beforeInterval,
|
||||||
|
afterFirstInterval,
|
||||||
|
}
|
||||||
|
}).pipe(
|
||||||
|
Effect.provide(TestClock.layer()),
|
||||||
|
))
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
returnsQuery: true,
|
||||||
|
beforeInterval: 1,
|
||||||
|
afterFirstInterval: 2,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("invalidateCacheEntry forces the next fetch for that key to rerun", async () => {
|
it("invalidateCacheEntry forces the next fetch for that key to rerun", async () => {
|
||||||
let calls = 0
|
let calls = 0
|
||||||
const key = staticKey<readonly [number]>([1])
|
const key = staticKey<readonly [number]>([1])
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Cause, type Context, Duration, Effect, Equal, type Equivalence, Exit, Fiber, Option, Pipeable, Predicate, PubSub, Ref, type Scope, Semaphore, Stream, SubscriptionRef } from "effect"
|
import { Cause, type Context, Duration, Effect, Equal, type Equivalence, Exit, Fiber, Function, Option, Pipeable, Predicate, PubSub, Ref, type Schedule, type Scope, Semaphore, Stream, SubscriptionRef } from "effect"
|
||||||
import { AsyncResult } from "effect/unstable/reactivity"
|
import { AsyncResult } from "effect/unstable/reactivity"
|
||||||
import * as Lens from "./Lens.js"
|
import * as Lens from "./Lens.js"
|
||||||
import * as QueryClient from "./QueryClient.js"
|
import * as QueryClient from "./QueryClient.js"
|
||||||
@@ -421,6 +421,43 @@ export const service = <K, A, E = never, R = never>(
|
|||||||
query => Effect.forkScoped(query.run),
|
query => Effect.forkScoped(query.run),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the Query on a schedule and returns it.
|
||||||
|
*
|
||||||
|
* @example Refresh every five minutes, starting after five minutes
|
||||||
|
* ```ts
|
||||||
|
* yield* query.pipe(
|
||||||
|
* Query.withScheduledRefresh(Schedule.spaced("5 minutes")),
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example Refresh at most three times
|
||||||
|
* ```ts
|
||||||
|
* yield* query.pipe(
|
||||||
|
* Query.withScheduledRefresh(
|
||||||
|
* Schedule.spaced("5 minutes").pipe(Schedule.upTo({ times: 3 })),
|
||||||
|
* ),
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const withScheduledRefresh: {
|
||||||
|
<Output, Error, Env>(
|
||||||
|
schedule: Schedule.Schedule<Output, unknown, Error, Env>,
|
||||||
|
): <K, A, E, R>(
|
||||||
|
self: Query<K, A, E, R>,
|
||||||
|
) => Effect.Effect<Query<K, A, E, R>, never, Scope.Scope | Env>
|
||||||
|
<K, A, E, R, Output, Error, Env>(
|
||||||
|
self: Query<K, A, E, R>,
|
||||||
|
schedule: Schedule.Schedule<Output, unknown, Error, Env>,
|
||||||
|
): Effect.Effect<Query<K, A, E, R>, never, Scope.Scope | Env>
|
||||||
|
} = Function.dual(2, <K, A, E, R, Output, Error, Env>(
|
||||||
|
self: Query<K, A, E, R>,
|
||||||
|
schedule: Schedule.Schedule<Output, unknown, Error, Env>,
|
||||||
|
) => Effect.schedule(self.refresh, schedule).pipe(
|
||||||
|
Effect.forkScoped,
|
||||||
|
Effect.as(self),
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
export class QueryStateLens<in out K, in out A, in out E = never>
|
export class QueryStateLens<in out K, in out A, in out E = never>
|
||||||
extends Lens.LensImpl<QueryState<K, A, E>, never, never, never, never> {
|
extends Lens.LensImpl<QueryState<K, A, E>, never, never, never, never> {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"effect-view": "workspace:*",
|
"effect-view": "workspace:*",
|
||||||
"vite": "^8.0.16",
|
"vite": "^8.0.16",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^4.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"effect-view": "^0.1.0",
|
"effect-view": "^0.1.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user