diff --git a/packages/effect-fc/test/Query.test.ts b/packages/effect-fc/test/Query.test.ts
new file mode 100644
index 0000000..ee6544f
--- /dev/null
+++ b/packages/effect-fc/test/Query.test.ts
@@ -0,0 +1,169 @@
+import { Effect, Option, type Scope, Stream } from "effect"
+import { describe, expect, it } from "vitest"
+import * as Query from "../src/Query.js"
+import * as QueryClient from "../src/QueryClient.js"
+import * as Result from "../src/Result.js"
+
+
+const runQueryTest = (effect: Effect.Effect) =>
+ Effect.runPromise(Effect.scoped(effect.pipe(
+ Effect.provide(QueryClient.QueryClient.Default),
+ )))
+
+const expectSuccessValue = (
+ result: Result.Result,
+): A => {
+ expect(Result.isSuccess(result)).toBe(true)
+
+ if (!Result.isSuccess(result))
+ throw new Error(`Expected Success result, received ${result._tag}`)
+
+ return result.value
+}
+
+const expectSomeValue = (option: Option.Option): A => {
+ expect(Option.isSome(option)).toBe(true)
+
+ if (!Option.isSome(option))
+ throw new Error("Expected Some option, received None")
+
+ return option.value
+}
+
+describe("Query", () => {
+ it("fetch caches successful results until they are invalidated or stale", async () => {
+ let calls = 0
+ const key = Stream.empty as Stream.Stream
+
+ const result = await runQueryTest(Effect.gen(function*() {
+ const query = yield* Query.make({
+ key,
+ f: ([id]: readonly [number]) => Effect.sync(() => {
+ calls += 1
+ return `value:${id}:${calls}`
+ }),
+ staleTime: "1 minute",
+ })
+
+ const first = yield* query.fetch([1])
+ const second = yield* query.fetch([1])
+
+ return [first, second] as const
+ }))
+
+ expect(calls).toBe(1)
+ expect(result[0]._tag).toBe("Success")
+ expect(result[1]._tag).toBe("Success")
+ expect(expectSuccessValue(result[0])).toBe("value:1:1")
+ expect(expectSuccessValue(result[1])).toBe("value:1:1")
+ })
+
+ it("refresh reruns the latest query key", async () => {
+ let calls = 0
+ const key = Stream.empty as Stream.Stream
+
+ const result = await runQueryTest(Effect.gen(function*() {
+ const query = yield* Query.make({
+ key,
+ f: ([id]: readonly [number]) => Effect.sync(() => {
+ calls += 1
+ return `value:${id}:${calls}`
+ }),
+ staleTime: "0 millis",
+ })
+
+ const first = yield* query.fetch([1])
+ yield* Effect.sleep("1 millis")
+ const refreshed = yield* query.refresh
+
+ return [first, refreshed] as const
+ }))
+
+ expect(calls).toBe(2)
+ expect(expectSuccessValue(result[0])).toBe("value:1:1")
+ expect(expectSuccessValue(result[1])).toBe("value:1:2")
+ })
+
+ it("invalidateCacheEntry forces the next fetch for that key to rerun", async () => {
+ let calls = 0
+ const key = Stream.empty as Stream.Stream
+
+ const result = await runQueryTest(Effect.gen(function*() {
+ const query = yield* Query.make({
+ key,
+ f: ([id]: readonly [number]) => Effect.sync(() => {
+ calls += 1
+ return `value:${id}:${calls}`
+ }),
+ staleTime: "1 minute",
+ })
+
+ const first = yield* query.fetch([1])
+ yield* query.invalidateCacheEntry([1])
+ const second = yield* query.fetch([1])
+
+ return [first, second] as const
+ }))
+
+ expect(calls).toBe(2)
+ expect(expectSuccessValue(result[0])).toBe("value:1:1")
+ expect(expectSuccessValue(result[1])).toBe("value:1:2")
+ })
+
+ it("invalidateCache clears cached entries for the query function", async () => {
+ let calls = 0
+ const key = Stream.empty as Stream.Stream
+
+ const result = await runQueryTest(Effect.gen(function*() {
+ const query = yield* Query.make({
+ key,
+ f: ([id]: readonly [number]) => Effect.sync(() => {
+ calls += 1
+ return `value:${id}:${calls}`
+ }),
+ staleTime: "1 minute",
+ })
+
+ const first = yield* query.fetch([1])
+ yield* query.invalidateCache
+ const second = yield* query.fetch([1])
+
+ return [first, second] as const
+ }))
+
+ expect(calls).toBe(2)
+ expect(expectSuccessValue(result[0])).toBe("value:1:1")
+ expect(expectSuccessValue(result[1])).toBe("value:1:2")
+ })
+
+ it("service starts the key stream automatically and updates latest state", async () => {
+ let calls = 0
+ const key = Stream.make([1] as const) as Stream.Stream
+
+ const effect = Effect.gen(function*() {
+ const query = yield* Query.service({
+ key,
+ f: ([id]: readonly [number]) => Effect.sync(() => {
+ calls += 1
+ return `value:${id}:${calls}`
+ }),
+ staleTime: "1 minute",
+ })
+
+ yield* Effect.sleep("10 millis")
+
+ return {
+ final: yield* query.result.get,
+ latestKey: yield* query.latestKey.get,
+ latestFinalResult: yield* query.latestFinalResult.get,
+ }
+ })
+
+ const result = await runQueryTest(effect)
+
+ expect(calls).toBe(1)
+ expect(expectSuccessValue(result.final)).toBe("value:1:1")
+ expect(expectSomeValue(result.latestKey)).toEqual([1])
+ expect(expectSuccessValue(expectSomeValue(result.latestFinalResult))).toBe("value:1:1")
+ })
+})