Phase 7c+7d: cache invalidation + offline data layer
7c: Client data cache management via element attributes (sx-cache-invalidate) and response headers (SX-Cache-Invalidate, SX-Cache-Update). Programmatic API: invalidate-page-cache, invalidate-all-page-cache, update-page-cache. 7d: Service Worker (sx-sw.js) with IndexedDB for offline-capable data caching. Network-first for /sx/data/ and /sx/io/, stale-while- revalidate for /static/. Cache invalidation propagates from in-memory cache to SW via postMessage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3551,6 +3551,27 @@ PLATFORM_ORCHESTRATION_JS = """
|
||||
});
|
||||
}
|
||||
|
||||
function parseSxData(text) {
|
||||
// Parse SX text into a data value. Returns the first parsed expression,
|
||||
// or NIL on error. Used by cache update directives.
|
||||
try {
|
||||
var exprs = parse(text);
|
||||
return exprs.length >= 1 ? exprs[0] : NIL;
|
||||
} catch (e) {
|
||||
logWarn("sx:cache parse error: " + (e && e.message ? e.message : e));
|
||||
return NIL;
|
||||
}
|
||||
}
|
||||
|
||||
function swPostMessage(msg) {
|
||||
// Send a message to the active service worker (if registered).
|
||||
// Used to notify SW of cache invalidation.
|
||||
if (typeof navigator !== "undefined" && navigator.serviceWorker &&
|
||||
navigator.serviceWorker.controller) {
|
||||
navigator.serviceWorker.controller.postMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function urlPathname(href) {
|
||||
try {
|
||||
return new URL(href, location.href).pathname;
|
||||
@@ -4025,6 +4046,14 @@ def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has
|
||||
}
|
||||
// Set up direct resolution for future chunks
|
||||
global.__sxResolve = function(id, sx) { resolveSuspense(id, sx); };
|
||||
// Register service worker for offline data caching
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("/sx-sw.js", { scope: "/" }).then(function(reg) {
|
||||
logInfo("sx:sw registered (scope: " + reg.scope + ")");
|
||||
}).catch(function(err) {
|
||||
logWarn("sx:sw registration failed: " + (err && err.message ? err.message : err));
|
||||
});
|
||||
}
|
||||
};
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", _sxInit);
|
||||
|
||||
@@ -154,17 +154,19 @@
|
||||
;; Extract all SX response header directives into a dict.
|
||||
;; get-header is (fn (name) → string or nil).
|
||||
(dict
|
||||
"redirect" (get-header "SX-Redirect")
|
||||
"refresh" (get-header "SX-Refresh")
|
||||
"trigger" (get-header "SX-Trigger")
|
||||
"retarget" (get-header "SX-Retarget")
|
||||
"reswap" (get-header "SX-Reswap")
|
||||
"location" (get-header "SX-Location")
|
||||
"replace-url" (get-header "SX-Replace-Url")
|
||||
"css-hash" (get-header "SX-Css-Hash")
|
||||
"trigger-swap" (get-header "SX-Trigger-After-Swap")
|
||||
"trigger-settle" (get-header "SX-Trigger-After-Settle")
|
||||
"content-type" (get-header "Content-Type"))))
|
||||
"redirect" (get-header "SX-Redirect")
|
||||
"refresh" (get-header "SX-Refresh")
|
||||
"trigger" (get-header "SX-Trigger")
|
||||
"retarget" (get-header "SX-Retarget")
|
||||
"reswap" (get-header "SX-Reswap")
|
||||
"location" (get-header "SX-Location")
|
||||
"replace-url" (get-header "SX-Replace-Url")
|
||||
"css-hash" (get-header "SX-Css-Hash")
|
||||
"trigger-swap" (get-header "SX-Trigger-After-Swap")
|
||||
"trigger-settle" (get-header "SX-Trigger-After-Settle")
|
||||
"content-type" (get-header "Content-Type")
|
||||
"cache-invalidate" (get-header "SX-Cache-Invalidate")
|
||||
"cache-update" (get-header "SX-Cache-Update"))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
@@ -195,6 +195,10 @@
|
||||
;; Triggers (before swap)
|
||||
(dispatch-trigger-events el (get resp-headers "trigger"))
|
||||
|
||||
;; Cache directives — process before navigation so cache is
|
||||
;; ready when the target page loads.
|
||||
(process-cache-directives el resp-headers text)
|
||||
|
||||
(cond
|
||||
;; Redirect
|
||||
(get resp-headers "redirect")
|
||||
@@ -589,6 +593,76 @@
|
||||
{"data" data "ts" (now-ms)})))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Client-side routing — cache management
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define invalidate-page-cache
|
||||
(fn (page-name)
|
||||
;; Clear cached data for a page. Removes all cache entries whose key
|
||||
;; matches page-name (exact) or starts with "page-name:" (with params).
|
||||
;; Also notifies the service worker to clear its IndexedDB entries.
|
||||
(for-each
|
||||
(fn (k)
|
||||
(when (or (= k page-name) (starts-with? k (str page-name ":")))
|
||||
(dict-set! _page-data-cache k nil)))
|
||||
(keys _page-data-cache))
|
||||
(sw-post-message {"type" "invalidate" "page" page-name})
|
||||
(log-info (str "sx:cache invalidate " page-name))))
|
||||
|
||||
(define invalidate-all-page-cache
|
||||
(fn ()
|
||||
;; Clear all cached page data and notify service worker.
|
||||
(set! _page-data-cache (dict))
|
||||
(sw-post-message {"type" "invalidate" "page" "*"})
|
||||
(log-info "sx:cache invalidate *")))
|
||||
|
||||
(define update-page-cache
|
||||
(fn (page-name data)
|
||||
;; Replace cached data for a page with server-provided data.
|
||||
;; Uses a bare page-name key (no params) — the server knows the
|
||||
;; canonical data shape for the page.
|
||||
(let ((cache-key (page-data-cache-key page-name (dict))))
|
||||
(page-data-cache-set cache-key data)
|
||||
(log-info (str "sx:cache update " page-name)))))
|
||||
|
||||
(define process-cache-directives
|
||||
(fn (el resp-headers response-text)
|
||||
;; Process cache invalidation and update directives from both
|
||||
;; element attributes and response headers.
|
||||
;;
|
||||
;; Element attributes (set by component author):
|
||||
;; sx-cache-invalidate="page-name" — clear page cache on success
|
||||
;; sx-cache-invalidate="*" — clear all page caches
|
||||
;;
|
||||
;; Response headers (set by server):
|
||||
;; SX-Cache-Invalidate: page-name — clear page cache
|
||||
;; SX-Cache-Update: page-name — replace cache with response data
|
||||
|
||||
;; 1. Element-level invalidation
|
||||
(let ((el-invalidate (dom-get-attr el "sx-cache-invalidate")))
|
||||
(when el-invalidate
|
||||
(if (= el-invalidate "*")
|
||||
(invalidate-all-page-cache)
|
||||
(invalidate-page-cache el-invalidate))))
|
||||
|
||||
;; 2. Response header invalidation
|
||||
(let ((hdr-invalidate (get resp-headers "cache-invalidate")))
|
||||
(when hdr-invalidate
|
||||
(if (= hdr-invalidate "*")
|
||||
(invalidate-all-page-cache)
|
||||
(invalidate-page-cache hdr-invalidate))))
|
||||
|
||||
;; 3. Response header cache update (server pushes fresh data)
|
||||
;; parse-sx-data is a platform-provided function that parses SX text
|
||||
;; into a data value (returns nil on parse error).
|
||||
(let ((hdr-update (get resp-headers "cache-update")))
|
||||
(when hdr-update
|
||||
(let ((data (parse-sx-data response-text)))
|
||||
(when data
|
||||
(update-page-cache hdr-update data)))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Client-side routing
|
||||
;; --------------------------------------------------------------------------
|
||||
@@ -1061,6 +1135,8 @@
|
||||
;; (resolve-page-data name params cb) → void; resolves data for a named page.
|
||||
;; Platform decides transport (HTTP, cache, IPC, etc). Calls (cb data-dict)
|
||||
;; when data is available. params is a dict of URL/route parameters.
|
||||
;; (parse-sx-data text) → parsed SX data value, or nil on error.
|
||||
;; Used by cache update to parse server-provided data in SX format.
|
||||
;;
|
||||
;; From boot.sx:
|
||||
;; _page-routes → list of route entries
|
||||
@@ -1080,4 +1156,8 @@
|
||||
;; (csrf-token) → string
|
||||
;; (cross-origin? url) → boolean
|
||||
;; (now-ms) → timestamp ms
|
||||
;;
|
||||
;; === Cache management ===
|
||||
;; (parse-sx-data text) → parsed SX data value, or nil on error
|
||||
;; (sw-post-message msg) → void; post message to active service worker
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user