Skip to main content

Documentation Index

Fetch the complete documentation index at: https://confect.dev/llms.txt

Use this file to discover all available pages before exploring further.

Convex caches the result of every query, keyed on its arguments and on everything the handler reads—including real wall-clock time. A query that observes time (directly via Date.now(), or indirectly through APIs that read it) is evicted from the cache much more aggressively and is re-executed on subsequent calls, even when its inputs are unchanged. Effect programs touch the clock far more often than ordinary JavaScript: forking a fiber, emitting a log, or opening a span all read the current time internally. Without intervention, every Confect-wrapped query would defeat the cache. To keep queries cacheable by default, Confect stubs the clock for the span of a query handler:
  • Date.now() returns 0.
  • The unsafe* methods on Effect’s Clock service (used internally by logging, span events, the default scheduler, and Fiber construction) return constants (0 and 0n).
  • The user-facing Clock.currentTimeMillis and Clock.currentTimeNanos effects are the only way to read real time, and they are an explicit opt-in to cache invalidation.
Mutations and actions are not affected by any of this. Real time is always available there, with no caching trade-offs.

What stays cached

The following do not observe real time from within a query handler and so do not interfere with caching:
  • Effect.log / Effect.logInfo / etc.
  • Effect.withSpan and other tracing primitives
  • Forking fibers, the default scheduler, and other Effect internals
  • A bare Date.now() call inside the handler (returns 0)

Opting in to real time

Use Effect’s Clock service when you need the real timestamp:
import { Clock, Effect } from "effect";
import { DatabaseReader } from "./confect/_generated/services";

Effect.gen(function* () {
  const reader = yield* DatabaseReader;
  const rows = yield* reader
    .table("notes")
    .index("by_creation_time")
    .collect();
  const fetchedAt = yield* Clock.currentTimeMillis;

  return { fetchedAt, rows };
});
Any query that reads Clock.currentTimeMillis or Clock.currentTimeNanos will be evicted from Convex’s cache and re-executed on every subscription update, matching Convex’s behavior for any query that observes real time.

Preferring cacheable alternatives

If you don’t actually need sub-second accuracy inside the query, follow Convex’s recommended patterns to preserve caching:
  • Pass a coarse timestamp (e.g. rounded to the minute or hour) as a query argument from the client.
  • Maintain a boolean or status field on the document, updated by a scheduled function or mutation.
  • Compute time-dependent values in a mutation or action and persist the result.

Caveats

Stubbing Date.now() inside the handler means library code that calls it during a query—logging timestamps, cache keys, ID generation—will see 0 rather than the real time. This is almost always what you want for a deterministic, cacheable query, but it can surprise code that was written assuming a real clock. If such code must run inside a query, route it through Clock.currentTimeMillis instead and accept the cache trade-off.