Add fetch_cached() client-side ORM cache with SPA auto-reset

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-02-20 19:58:32 +00:00
parent b5eb27a827
commit f58aa994ed
4 changed files with 173 additions and 53 deletions

View File

@@ -16,6 +16,10 @@
* @Instantiatable
*/
class Rsx_Js_Model {
// Client-side fetch cache: Map<"ModelName:id", Promise<model>>
// Reset on SPA navigation, manual reset via orm_cache_reset()
static _fetch_cache = new Map();
/**
* Constructor - Initialize model instance with data
*
@@ -77,7 +81,11 @@ class Rsx_Js_Model {
const response = await Orm_Controller.fetch({ model: modelName, id: id });
// Response is already hydrated by Ajax layer (Ajax.js calls _instantiate_models_recursive)
// Just return the response directly
// Warm fetch cache for subsequent fetch_cached() calls
const cache_key = `${modelName}:${id}`;
Rsx_Js_Model._fetch_cache.set(cache_key, Promise.resolve(response));
return response;
}
@@ -100,9 +108,83 @@ class Rsx_Js_Model {
// Pass or_null flag to get null instead of exception
const response = await Orm_Controller.fetch({ model: modelName, id: id, or_null: true });
// Warm fetch cache on successful fetch (don't cache nulls)
if (response !== null) {
const cache_key = `${modelName}:${id}`;
Rsx_Js_Model._fetch_cache.set(cache_key, Promise.resolve(response));
}
return response;
}
/**
* Fetch record using in-memory cache
*
* Returns cached result if available, otherwise calls fetch() and caches the result.
* Stores promises so concurrent calls for the same model/id share a single backend
* request. Cache automatically resets on SPA navigation.
*
* Intended for non-critical display data (names, labels, log history) where freshness
* is not essential. Do NOT use for data that must reflect recent edits.
*
* @param {number} id - ID to fetch
* @returns {Promise} - Model instance (from cache or fresh fetch)
* @throws {Error} - If record not found or access denied
*/
static async fetch_cached(id) {
const CurrentClass = this;
const modelName = CurrentClass.__MODEL || CurrentClass.name;
const cache_key = `${modelName}:${id}`;
if (Rsx_Js_Model._fetch_cache.has(cache_key)) {
return Rsx_Js_Model._fetch_cache.get(cache_key);
}
// Store the promise itself - concurrent calls for same model/id
// await the same promise (single backend request)
const promise = CurrentClass.fetch(id);
Rsx_Js_Model._fetch_cache.set(cache_key, promise);
try {
const result = await promise;
return result;
} catch (e) {
// Failed fetches removed from cache so next attempt retries
Rsx_Js_Model._fetch_cache.delete(cache_key);
throw e;
}
}
/**
* Reset the ORM fetch cache
*
* Called automatically on SPA navigation. Can also be called manually
* after save operations to ensure subsequent fetch_cached() calls
* retrieve fresh data.
*
* @param {string} [model_name] - Clear specific model only (all IDs)
* @param {number} [id] - Clear specific model+id (requires model_name)
*/
static orm_cache_reset(model_name, id) {
if (!model_name) {
Rsx_Js_Model._fetch_cache.clear();
return;
}
if (id !== undefined) {
Rsx_Js_Model._fetch_cache.delete(`${model_name}:${id}`);
return;
}
// Clear all entries for specific model
const prefix = `${model_name}:`;
for (const key of Rsx_Js_Model._fetch_cache.keys()) {
if (key.startsWith(prefix)) {
Rsx_Js_Model._fetch_cache.delete(key);
}
}
}
/**
* Get the PHP model class name
* Used internally for API calls