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()returns0.- The
unsafe*methods on Effect’sClockservice (used internally by logging, span events, the default scheduler, andFiberconstruction) return constants (0and0n). - The user-facing
Clock.currentTimeMillisandClock.currentTimeNanoseffects are the only way to read real time, and they are an explicit opt-in to cache invalidation.
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.withSpanand other tracing primitives- Forking fibers, the default scheduler, and other Effect internals
- A bare
Date.now()call inside the handler (returns0)
Opting in to real time
Use Effect’sClock service when you need the real timestamp:
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
StubbingDate.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.