From ff63a88204024cab2c86a522350928f8d15da237 Mon Sep 17 00:00:00 2001 From: Kyle Bernhardy Date: Thu, 2 Jul 2026 10:08:45 -0600 Subject: [PATCH 1/5] Document loadedFromSource cache disposition on the request context Companion to HarperFast/harper#1575 (fixes HarperFast/harper#1571): context.loadedFromSource is now assigned on caching-table gets. Adds an "Observing cache disposition" section under sourcedFrom with the exact settled semantics (staleIfError=true, stale-while-revalidate=false, dedupe-join=false, last get wins) and lists the property in both getContext() Context references. Co-Authored-By: Claude Fable 5 --- reference/resources/resource-api.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index e87c1574..555bbf17 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -248,6 +248,24 @@ Options (all optional; prefer setting these via `@table` schema directives): Harper automatically serializes concurrent requests for the same missing or stale record — all waiting requests share a single upstream fetch, preventing cache stampedes. +#### Observing cache disposition + +Each `get` on a caching table records whether the record came from the cache or from the source, in the `loadedFromSource` property of both the request context and the `RequestTarget`: + +```javascript +const context = {}; +const record = await MyCache.get(recordId, context); +console.log(context.loadedFromSource); // true = fetched from source, false = served from cache +``` + +Within a resource method, the same value is available on the active context via `getContext().loadedFromSource` after the `get` resolves. The flag settles as follows: + +- `true` — the get fetched the record from the source, including when a source error fell back to a stale record (`staleIfError`). +- `false` — the record was served from the cache: fresh hits, `onlyIfCached` requests, stale-while-revalidate responses (the source fetch continues in the background), and requests that waited on another request's in-flight fetch of the same record. This last case means a cache hit can still take as long as an upstream fetch. +- Each get on a caching table in the same context overwrites the value, so read it after the `get` you are measuring. + +Resource instances also expose this as [`wasLoadedFromSource()`](#wasloadedfromsource-boolean). Prior to Harper 5.1.16, the flag was only observable via an explicitly passed `RequestTarget`; `context.loadedFromSource` was never assigned. + #### Source `get` — controlling timestamp and expiration Inside a source `get()` method, the context (`this.getContext()`) exposes caching-specific properties: @@ -641,6 +659,7 @@ Returns the current context, which includes: - `user` — User object with username, role, and authorization information - `transaction` — The current transaction +- `loadedFromSource` — For caching tables, cache disposition of the most recent `get` in this context: `true` if it fetched from the source, `false` if served from cache (see [Observing cache disposition](#observing-cache-disposition)) When triggered by HTTP, the context is the `Request` object with these additional properties: @@ -1146,6 +1165,7 @@ getContext is availabe as export from the `harper` module, or as a global variab - `user` — User object with username, role, and authorization information - `transaction` — The current transaction +- `loadedFromSource` — For caching tables, cache disposition of the most recent `get` in this context: `true` if it fetched from the source, `false` if served from cache (see [Observing cache disposition](#observing-cache-disposition)) When triggered by HTTP, the context is the `Request` object with these additional properties: From 9b3ed22749039155cfe908b1a24358e4af1e94d1 Mon Sep 17 00:00:00 2001 From: Kyle Bernhardy Date: Thu, 2 Jul 2026 10:18:03 -0600 Subject: [PATCH 2/5] Do not point readers at wasLoadedFromSource() for cache disposition Gemini review: get() returns a plain RecordObject, not a resource instance, so the method is not reachable where developers would want it (and the base implementation is an unoverridden stub). Direct readers to the context / RequestTarget instead. Co-Authored-By: Claude Fable 5 --- reference/resources/resource-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index 555bbf17..ce2485e3 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -264,7 +264,7 @@ Within a resource method, the same value is available on the active context via - `false` — the record was served from the cache: fresh hits, `onlyIfCached` requests, stale-while-revalidate responses (the source fetch continues in the background), and requests that waited on another request's in-flight fetch of the same record. This last case means a cache hit can still take as long as an upstream fetch. - Each get on a caching table in the same context overwrites the value, so read it after the `get` you are measuring. -Resource instances also expose this as [`wasLoadedFromSource()`](#wasloadedfromsource-boolean). Prior to Harper 5.1.16, the flag was only observable via an explicitly passed `RequestTarget`; `context.loadedFromSource` was never assigned. +Note that `get()` returns a plain `RecordObject`, not a resource instance — the record itself does not carry cache disposition; read it from the context (or an explicitly passed `RequestTarget`). Prior to Harper 5.1.16, the flag was only observable via an explicitly passed `RequestTarget`; `context.loadedFromSource` was never assigned. #### Source `get` — controlling timestamp and expiration From c589433a10eb1cd1efbd344ef5db1911357f6ce3 Mon Sep 17 00:00:00 2001 From: Kyle Bernhardy Date: Thu, 2 Jul 2026 10:19:26 -0600 Subject: [PATCH 3/5] Clarify that loadedFromSource=true means a source request was made, and version-gate the Context entries Codex review: staleIfError fallbacks serve stale cached data on a failed source fetch, so "fetched from source" overclaimed freshness; and the Context list entries needed the 5.1.16+ availability caveat. Co-Authored-By: Claude Fable 5 --- reference/resources/resource-api.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index ce2485e3..e30dc2f2 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -255,12 +255,12 @@ Each `get` on a caching table records whether the record came from the cache or ```javascript const context = {}; const record = await MyCache.get(recordId, context); -console.log(context.loadedFromSource); // true = fetched from source, false = served from cache +console.log(context.loadedFromSource); // true = went to the source, false = served from cache ``` Within a resource method, the same value is available on the active context via `getContext().loadedFromSource` after the `get` resolves. The flag settles as follows: -- `true` — the get fetched the record from the source, including when a source error fell back to a stale record (`staleIfError`). +- `true` — the get went to the source: either it fetched the record, or the source errored and a stale cached record was served as a fallback (`staleIfError`). `true` means a source request was made, not necessarily that the returned data is fresh. - `false` — the record was served from the cache: fresh hits, `onlyIfCached` requests, stale-while-revalidate responses (the source fetch continues in the background), and requests that waited on another request's in-flight fetch of the same record. This last case means a cache hit can still take as long as an upstream fetch. - Each get on a caching table in the same context overwrites the value, so read it after the `get` you are measuring. @@ -659,7 +659,7 @@ Returns the current context, which includes: - `user` — User object with username, role, and authorization information - `transaction` — The current transaction -- `loadedFromSource` — For caching tables, cache disposition of the most recent `get` in this context: `true` if it fetched from the source, `false` if served from cache (see [Observing cache disposition](#observing-cache-disposition)) +- `loadedFromSource` — For caching tables (5.1.16+), cache disposition of the most recent `get` in this context: `true` if it went to the source, `false` if served from cache (see [Observing cache disposition](#observing-cache-disposition)) When triggered by HTTP, the context is the `Request` object with these additional properties: @@ -1165,7 +1165,7 @@ getContext is availabe as export from the `harper` module, or as a global variab - `user` — User object with username, role, and authorization information - `transaction` — The current transaction -- `loadedFromSource` — For caching tables, cache disposition of the most recent `get` in this context: `true` if it fetched from the source, `false` if served from cache (see [Observing cache disposition](#observing-cache-disposition)) +- `loadedFromSource` — For caching tables (5.1.16+), cache disposition of the most recent `get` in this context: `true` if it went to the source, `false` if served from cache (see [Observing cache disposition](#observing-cache-disposition)) When triggered by HTTP, the context is the `Request` object with these additional properties: From ed8edaa969bb5dfbe9453485871a05d90bc2d4b6 Mon Sep 17 00:00:00 2001 From: Kyle Bernhardy Date: Thu, 2 Jul 2026 11:29:59 -0600 Subject: [PATCH 4/5] Document the implemented wasLoadedFromSource() (harper#1577) Companion to HarperFast/harper#1577 (fixes HarperFast/harper#1576): the method now returns the recorded cache disposition on caching-table resource instances instead of always undefined. Corrects its reference section and restores the instance-method mention in Observing cache disposition. Co-Authored-By: Claude Fable 5 --- reference/resources/resource-api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index e30dc2f2..a49f63b2 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -264,7 +264,7 @@ Within a resource method, the same value is available on the active context via - `false` — the record was served from the cache: fresh hits, `onlyIfCached` requests, stale-while-revalidate responses (the source fetch continues in the background), and requests that waited on another request's in-flight fetch of the same record. This last case means a cache hit can still take as long as an upstream fetch. - Each get on a caching table in the same context overwrites the value, so read it after the `get` you are measuring. -Note that `get()` returns a plain `RecordObject`, not a resource instance — the record itself does not carry cache disposition; read it from the context (or an explicitly passed `RequestTarget`). Prior to Harper 5.1.16, the flag was only observable via an explicitly passed `RequestTarget`; `context.loadedFromSource` was never assigned. +Note that `get()` returns a plain `RecordObject`, not a resource instance — the record itself does not carry cache disposition; read it from the context (or an explicitly passed `RequestTarget`). In custom resource methods, where `this` is the loaded resource instance, the same value is available as [`this.wasLoadedFromSource()`](#wasloadedfromsource-boolean) (5.1.16+). Prior to Harper 5.1.16, the flag was only observable via an explicitly passed `RequestTarget`; `context.loadedFromSource` was never assigned and `wasLoadedFromSource()` always returned `undefined`. #### Source `get` — controlling timestamp and expiration @@ -433,7 +433,7 @@ Post.sourcedFrom(BlogSource); ### `wasLoadedFromSource(): boolean` -For caching tables, indicates that this request was a cache miss and the data was loaded from the source resource. +For caching tables (5.1.16+), reports whether this resource's record was loaded from the source — the same settled semantics as [`context.loadedFromSource`](#observing-cache-disposition). Returns `undefined` until a load resolves, on tables without a source `get`, and on all Harper versions before 5.1.16 (the method previously existed but was never assigned a value). --- From 0862bdaa28a87c8abc0bdc252d3d0b9fe5605aa4 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Thu, 2 Jul 2026 16:30:46 -0600 Subject: [PATCH 5/5] docs(resources): drop wasLoadedFromSource(), keep context/target.loadedFromSource (#1576) wasLoadedFromSource() is an unoverridden stub that always returns undefined; we've standardized on context.loadedFromSource / target.loadedFromSource as the way to observe cache disposition (harper#1576). Remove the reference section and the cross-reference from the caching-disposition note. Co-Authored-By: Claude Opus 4.8 --- reference/resources/resource-api.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/reference/resources/resource-api.md b/reference/resources/resource-api.md index a49f63b2..58712ee3 100644 --- a/reference/resources/resource-api.md +++ b/reference/resources/resource-api.md @@ -264,7 +264,7 @@ Within a resource method, the same value is available on the active context via - `false` — the record was served from the cache: fresh hits, `onlyIfCached` requests, stale-while-revalidate responses (the source fetch continues in the background), and requests that waited on another request's in-flight fetch of the same record. This last case means a cache hit can still take as long as an upstream fetch. - Each get on a caching table in the same context overwrites the value, so read it after the `get` you are measuring. -Note that `get()` returns a plain `RecordObject`, not a resource instance — the record itself does not carry cache disposition; read it from the context (or an explicitly passed `RequestTarget`). In custom resource methods, where `this` is the loaded resource instance, the same value is available as [`this.wasLoadedFromSource()`](#wasloadedfromsource-boolean) (5.1.16+). Prior to Harper 5.1.16, the flag was only observable via an explicitly passed `RequestTarget`; `context.loadedFromSource` was never assigned and `wasLoadedFromSource()` always returned `undefined`. +Note that `get()` returns a plain `RecordObject`, not a resource instance — the record itself does not carry cache disposition; read it from the context (or an explicitly passed `RequestTarget`). Prior to Harper 5.1.16, `context.loadedFromSource` was never assigned and the flag was only observable via an explicitly passed `RequestTarget`. #### Source `get` — controlling timestamp and expiration @@ -431,12 +431,6 @@ Post.sourcedFrom(BlogSource); --- -### `wasLoadedFromSource(): boolean` - -For caching tables (5.1.16+), reports whether this resource's record was loaded from the source — the same settled semantics as [`context.loadedFromSource`](#observing-cache-disposition). Returns `undefined` until a load resolves, on tables without a source `get`, and on all Harper versions before 5.1.16 (the method previously existed but was never assigned a value). - ---- - ### `getUpdatedTime(): number` Returns the last updated time of the resource (milliseconds since epoch).