Add :effects annotations to all spec files and update bootstrappers
Bootstrappers (bootstrap_py.py, js.sx) now skip :effects keyword in define forms, enabling effect annotations throughout the spec without changing generated output. Annotated 180+ functions across 14 spec files: - signals.sx: signal/deref [] pure, reset!/swap!/effect/batch [mutation] - engine.sx: parse-* [] pure, morph-*/swap-* [mutation io] - orchestration.sx: all [mutation io] (browser event binding) - adapter-html.sx: render-* [render] - adapter-dom.sx: render-* [render], reactive-* [render mutation] - adapter-sx.sx: aser-* [render] - adapter-async.sx: async-render-*/async-aser-* [render io] - parser.sx: all [] pure - render.sx: predicates [] pure, process-bindings [mutation] - boot.sx: all [mutation io] (browser init) - deps.sx: scan-*/transitive-* [] pure, compute-all-* [mutation] - router.sx: all [] pure (URL matching) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
;; Event dispatch helpers
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define dispatch-trigger-events
|
||||
(define dispatch-trigger-events :effects [mutation io]
|
||||
(fn (el (header-val :as string))
|
||||
;; Dispatch events from SX-Trigger / SX-Trigger-After-Swap headers.
|
||||
;; Value can be JSON object (name → detail) or comma-separated names.
|
||||
@@ -58,7 +58,7 @@
|
||||
;; CSS tracking
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define init-css-tracking
|
||||
(define init-css-tracking :effects [mutation io]
|
||||
(fn ()
|
||||
;; Read initial CSS hash from meta tag
|
||||
(let ((meta (dom-query "meta[name=\"sx-css-classes\"]")))
|
||||
@@ -72,7 +72,7 @@
|
||||
;; Request execution
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define execute-request
|
||||
(define execute-request :effects [mutation io]
|
||||
(fn (el (verbInfo :as dict) (extraParams :as dict))
|
||||
;; Gate checks then delegate to do-fetch.
|
||||
;; verbInfo: dict with "method" and "url" (or nil to read from element).
|
||||
@@ -105,7 +105,7 @@
|
||||
extraParams))))))))))))
|
||||
|
||||
|
||||
(define do-fetch
|
||||
(define do-fetch :effects [mutation io]
|
||||
(fn (el (verb :as string) (method :as string) (url :as string) (extraParams :as dict))
|
||||
;; Execute the actual fetch. Manages abort, headers, body, loading state.
|
||||
(let ((sync (dom-get-attr el "sx-sync")))
|
||||
@@ -201,7 +201,7 @@
|
||||
(dict "error" err))))))))))))
|
||||
|
||||
|
||||
(define handle-fetch-success
|
||||
(define handle-fetch-success :effects [mutation io]
|
||||
(fn (el (url :as string) (verb :as string) (extraParams :as dict) get-header (text :as string))
|
||||
;; Route a successful response through the appropriate handler.
|
||||
(let ((resp-headers (process-response-headers get-header)))
|
||||
@@ -269,7 +269,7 @@
|
||||
(dict "target" target-el "swap" swap-style)))))))
|
||||
|
||||
|
||||
(define handle-sx-response
|
||||
(define handle-sx-response :effects [mutation io]
|
||||
(fn (el target (text :as string) (swap-style :as string) (use-transition :as boolean))
|
||||
;; Handle SX-format response: strip components, extract CSS, render, swap.
|
||||
(let ((cleaned (strip-component-scripts text)))
|
||||
@@ -300,7 +300,7 @@
|
||||
(post-swap target)))))))))))
|
||||
|
||||
|
||||
(define handle-html-response
|
||||
(define handle-html-response :effects [mutation io]
|
||||
(fn (el target (text :as string) (swap-style :as string) (use-transition :as boolean))
|
||||
;; Handle HTML-format response: parse, OOB, select, swap.
|
||||
(let ((doc (dom-parse-html-document text)))
|
||||
@@ -337,7 +337,7 @@
|
||||
;; Retry
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define handle-retry
|
||||
(define handle-retry :effects [mutation io]
|
||||
(fn (el (verb :as string) (method :as string) (url :as string) (extraParams :as dict))
|
||||
;; Handle retry on failure if sx-retry is configured
|
||||
(let ((retry-attr (dom-get-attr el "sx-retry"))
|
||||
@@ -357,7 +357,7 @@
|
||||
;; Trigger binding
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define bind-triggers
|
||||
(define bind-triggers :effects [mutation io]
|
||||
(fn (el (verbInfo :as dict))
|
||||
;; Bind triggers from sx-trigger attribute (or defaults)
|
||||
(let ((triggers (or (parse-trigger-spec (dom-get-attr el "sx-trigger"))
|
||||
@@ -392,7 +392,7 @@
|
||||
triggers))))
|
||||
|
||||
|
||||
(define bind-event
|
||||
(define bind-event :effects [mutation io]
|
||||
(fn (el (event-name :as string) (mods :as dict) (verbInfo :as dict))
|
||||
;; Bind a standard DOM event trigger.
|
||||
;; Handles delay, once, changed, optimistic, preventDefault.
|
||||
@@ -453,7 +453,7 @@
|
||||
;; Post-swap lifecycle
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define post-swap
|
||||
(define post-swap :effects [mutation io]
|
||||
(fn (root)
|
||||
;; Run lifecycle after swap: activate scripts, process SX, hydrate, process
|
||||
(activate-scripts root)
|
||||
@@ -474,7 +474,7 @@
|
||||
;;
|
||||
;; Example: (button :sx-get "/search" :sx-on-settle "(reset! (use-store \"count\") 0)")
|
||||
|
||||
(define process-settle-hooks
|
||||
(define process-settle-hooks :effects [mutation io]
|
||||
(fn (el)
|
||||
(let ((settle-expr (dom-get-attr el "sx-on-settle")))
|
||||
(when (and settle-expr (not (empty? settle-expr)))
|
||||
@@ -484,7 +484,7 @@
|
||||
exprs))))))
|
||||
|
||||
|
||||
(define activate-scripts
|
||||
(define activate-scripts :effects [mutation io]
|
||||
(fn (root)
|
||||
;; Re-activate scripts in swapped content.
|
||||
;; Scripts inserted via innerHTML are inert — clone to make them execute.
|
||||
@@ -505,7 +505,7 @@
|
||||
;; OOB swap processing
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define process-oob-swaps
|
||||
(define process-oob-swaps :effects [mutation io]
|
||||
(fn (container (swap-fn :as lambda))
|
||||
;; Find and process out-of-band swaps in container.
|
||||
;; swap-fn is (fn (target oob-element swap-type) ...).
|
||||
@@ -529,7 +529,7 @@
|
||||
;; Head element hoisting
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define hoist-head-elements
|
||||
(define hoist-head-elements :effects [mutation io]
|
||||
(fn (container)
|
||||
;; Move style[data-sx-css] and link[rel=stylesheet] to <head>
|
||||
;; so they take effect globally.
|
||||
@@ -551,7 +551,7 @@
|
||||
;; Boost processing
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define process-boosted
|
||||
(define process-boosted :effects [mutation io]
|
||||
(fn (root)
|
||||
;; Find [sx-boost] containers and boost their descendants
|
||||
(for-each
|
||||
@@ -560,7 +560,7 @@
|
||||
(dom-query-all (or root (dom-body)) "[sx-boost]"))))
|
||||
|
||||
|
||||
(define boost-descendants
|
||||
(define boost-descendants :effects [mutation io]
|
||||
(fn (container)
|
||||
;; Boost links and forms within a container.
|
||||
;; The sx-boost attribute value is the default target selector
|
||||
@@ -609,7 +609,7 @@
|
||||
(define _page-data-cache (dict))
|
||||
(define _page-data-cache-ttl 30000) ;; 30 seconds in ms
|
||||
|
||||
(define page-data-cache-key
|
||||
(define page-data-cache-key :effects []
|
||||
(fn ((page-name :as string) (params :as dict))
|
||||
;; Build a cache key from page name + params.
|
||||
;; Params are from route matching so order is deterministic.
|
||||
@@ -623,7 +623,7 @@
|
||||
(keys params))
|
||||
(str base ":" (join "&" parts)))))))
|
||||
|
||||
(define page-data-cache-get
|
||||
(define page-data-cache-get :effects [mutation io]
|
||||
(fn ((cache-key :as string))
|
||||
;; Return cached data if fresh, else nil.
|
||||
(let ((entry (get _page-data-cache cache-key)))
|
||||
@@ -635,7 +635,7 @@
|
||||
nil)
|
||||
(get entry "data"))))))
|
||||
|
||||
(define page-data-cache-set
|
||||
(define page-data-cache-set :effects [mutation io]
|
||||
(fn ((cache-key :as string) data)
|
||||
;; Store data with current timestamp.
|
||||
(dict-set! _page-data-cache cache-key
|
||||
@@ -646,7 +646,7 @@
|
||||
;; Client-side routing — cache management
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define invalidate-page-cache
|
||||
(define invalidate-page-cache :effects [mutation io]
|
||||
(fn ((page-name :as string))
|
||||
;; Clear cached data for a page. Removes all cache entries whose key
|
||||
;; matches page-name (exact) or starts with "page-name:" (with params).
|
||||
@@ -659,14 +659,14 @@
|
||||
(sw-post-message {"type" "invalidate" "page" page-name})
|
||||
(log-info (str "sx:cache invalidate " page-name))))
|
||||
|
||||
(define invalidate-all-page-cache
|
||||
(define invalidate-all-page-cache :effects [mutation io]
|
||||
(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
|
||||
(define update-page-cache :effects [mutation io]
|
||||
(fn ((page-name :as string) data)
|
||||
;; Replace cached data for a page with server-provided data.
|
||||
;; Uses a bare page-name key (no params) — the server knows the
|
||||
@@ -675,7 +675,7 @@
|
||||
(page-data-cache-set cache-key data)
|
||||
(log-info (str "sx:cache update " page-name)))))
|
||||
|
||||
(define process-cache-directives
|
||||
(define process-cache-directives :effects [mutation io]
|
||||
(fn (el (resp-headers :as dict) (response-text :as string))
|
||||
;; Process cache invalidation and update directives from both
|
||||
;; element attributes and response headers.
|
||||
@@ -721,7 +721,7 @@
|
||||
|
||||
(define _optimistic-snapshots (dict))
|
||||
|
||||
(define optimistic-cache-update
|
||||
(define optimistic-cache-update :effects [mutation]
|
||||
(fn ((cache-key :as string) (mutator :as lambda))
|
||||
;; Apply predicted mutation to cached data. Saves snapshot for rollback.
|
||||
;; Returns predicted data or nil if no cached data exists.
|
||||
@@ -734,7 +734,7 @@
|
||||
(page-data-cache-set cache-key predicted)
|
||||
predicted)))))
|
||||
|
||||
(define optimistic-cache-revert
|
||||
(define optimistic-cache-revert :effects [mutation]
|
||||
(fn ((cache-key :as string))
|
||||
;; Revert to pre-mutation snapshot. Returns restored data or nil.
|
||||
(let ((snapshot (get _optimistic-snapshots cache-key)))
|
||||
@@ -743,12 +743,12 @@
|
||||
(dict-delete! _optimistic-snapshots cache-key)
|
||||
snapshot))))
|
||||
|
||||
(define optimistic-cache-confirm
|
||||
(define optimistic-cache-confirm :effects [mutation]
|
||||
(fn ((cache-key :as string))
|
||||
;; Server accepted — discard the rollback snapshot.
|
||||
(dict-delete! _optimistic-snapshots cache-key)))
|
||||
|
||||
(define submit-mutation
|
||||
(define submit-mutation :effects [mutation io]
|
||||
(fn ((page-name :as string) (params :as dict) (action-name :as string) payload (mutator-fn :as lambda) (on-complete :as lambda))
|
||||
;; Optimistic mutation: predict locally, send to server, confirm or revert.
|
||||
;; on-complete is called with "confirmed" or "reverted" status.
|
||||
@@ -787,14 +787,14 @@
|
||||
(define _is-online true)
|
||||
(define _offline-queue (list))
|
||||
|
||||
(define offline-is-online?
|
||||
(define offline-is-online? :effects [io]
|
||||
(fn () _is-online))
|
||||
|
||||
(define offline-set-online!
|
||||
(define offline-set-online! :effects [mutation]
|
||||
(fn ((val :as boolean))
|
||||
(set! _is-online val)))
|
||||
|
||||
(define offline-queue-mutation
|
||||
(define offline-queue-mutation :effects [mutation io]
|
||||
(fn ((action-name :as string) payload (page-name :as string) (params :as dict) (mutator-fn :as lambda))
|
||||
;; Queue a mutation for later sync. Apply optimistic update locally.
|
||||
(let ((cache-key (page-data-cache-key page-name params))
|
||||
@@ -813,7 +813,7 @@
|
||||
(log-info (str "sx:offline queued " action-name " (" (len _offline-queue) " pending)"))
|
||||
entry)))
|
||||
|
||||
(define offline-sync
|
||||
(define offline-sync :effects [mutation io]
|
||||
(fn ()
|
||||
;; Replay all pending mutations. Called on reconnect.
|
||||
(let ((pending (filter (fn ((e :as dict)) (= (get e "status") "pending")) _offline-queue)))
|
||||
@@ -830,11 +830,11 @@
|
||||
(log-warn (str "sx:offline sync failed " (get entry "action") ": " error)))))
|
||||
pending)))))
|
||||
|
||||
(define offline-pending-count
|
||||
(define offline-pending-count :effects [io]
|
||||
(fn ()
|
||||
(len (filter (fn ((e :as dict)) (= (get e "status") "pending")) _offline-queue))))
|
||||
|
||||
(define offline-aware-mutation
|
||||
(define offline-aware-mutation :effects [mutation io]
|
||||
(fn ((page-name :as string) (params :as dict) (action-name :as string) payload (mutator-fn :as lambda) (on-complete :as lambda))
|
||||
;; Top-level mutation function. Routes to submit-mutation when online,
|
||||
;; offline-queue-mutation when offline.
|
||||
@@ -849,7 +849,7 @@
|
||||
;; Client-side routing
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define current-page-layout
|
||||
(define current-page-layout :effects [io]
|
||||
(fn ()
|
||||
;; Find the layout name of the currently displayed page by matching
|
||||
;; the browser URL against the page route table.
|
||||
@@ -859,7 +859,7 @@
|
||||
(or (get match "layout") "")))))
|
||||
|
||||
|
||||
(define swap-rendered-content
|
||||
(define swap-rendered-content :effects [mutation io]
|
||||
(fn (target rendered (pathname :as string))
|
||||
;; Swap rendered DOM content into target and run post-processing.
|
||||
;; Shared by pure and data page client routes.
|
||||
@@ -875,7 +875,7 @@
|
||||
(log-info (str "sx:route client " pathname)))))
|
||||
|
||||
|
||||
(define resolve-route-target
|
||||
(define resolve-route-target :effects [io]
|
||||
(fn ((target-sel :as string))
|
||||
;; Resolve a target selector to a DOM element, or nil.
|
||||
(if (and target-sel (not (= target-sel "true")))
|
||||
@@ -883,7 +883,7 @@
|
||||
nil)))
|
||||
|
||||
|
||||
(define deps-satisfied?
|
||||
(define deps-satisfied? :effects [io]
|
||||
(fn ((match :as dict))
|
||||
;; Check if all component deps for a page are loaded client-side.
|
||||
(let ((deps (get match "deps"))
|
||||
@@ -893,7 +893,7 @@
|
||||
(every? (fn ((dep :as string)) (contains? loaded dep)) deps)))))
|
||||
|
||||
|
||||
(define try-client-route
|
||||
(define try-client-route :effects [mutation io]
|
||||
(fn ((pathname :as string) (target-sel :as string))
|
||||
;; Try to render a page client-side. Returns true if successful, false otherwise.
|
||||
;; target-sel is the CSS selector for the swap target (from sx-boost value).
|
||||
@@ -1011,7 +1011,7 @@
|
||||
true))))))))))))))))))
|
||||
|
||||
|
||||
(define bind-client-route-link
|
||||
(define bind-client-route-link :effects [mutation io]
|
||||
(fn (link (href :as string))
|
||||
;; Bind a boost link with client-side routing. If the route can be
|
||||
;; rendered client-side (pure page, no :data), do so. Otherwise
|
||||
@@ -1026,7 +1026,7 @@
|
||||
;; SSE processing
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define process-sse
|
||||
(define process-sse :effects [mutation io]
|
||||
(fn (root)
|
||||
;; Find and bind SSE elements
|
||||
(for-each
|
||||
@@ -1037,7 +1037,7 @@
|
||||
(dom-query-all (or root (dom-body)) "[sx-sse]"))))
|
||||
|
||||
|
||||
(define bind-sse
|
||||
(define bind-sse :effects [mutation io]
|
||||
(fn (el)
|
||||
;; Connect to SSE endpoint and bind swap handler
|
||||
(let ((url (dom-get-attr el "sx-sse")))
|
||||
@@ -1049,7 +1049,7 @@
|
||||
(bind-sse-swap el data))))))))
|
||||
|
||||
|
||||
(define bind-sse-swap
|
||||
(define bind-sse-swap :effects [mutation io]
|
||||
(fn (el (data :as string))
|
||||
;; Handle an SSE event: swap data into element
|
||||
(let ((target (resolve-target el))
|
||||
@@ -1081,7 +1081,7 @@
|
||||
;; Inline event handlers
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define bind-inline-handlers
|
||||
(define bind-inline-handlers :effects [mutation io]
|
||||
(fn (root)
|
||||
;; Find elements with sx-on:* attributes and bind SX event handlers.
|
||||
;; Handler bodies are SX expressions evaluated with `event` and `this`
|
||||
@@ -1115,7 +1115,7 @@
|
||||
;; Preload
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define bind-preload-for
|
||||
(define bind-preload-for :effects [mutation io]
|
||||
(fn (el)
|
||||
;; Bind preload event listeners based on sx-preload attribute
|
||||
(let ((preload-attr (dom-get-attr el "sx-preload")))
|
||||
@@ -1134,7 +1134,7 @@
|
||||
(loaded-component-names) _css-hash)))))))))))
|
||||
|
||||
|
||||
(define do-preload
|
||||
(define do-preload :effects [mutation io]
|
||||
(fn ((url :as string) (headers :as dict))
|
||||
;; Execute a preload fetch into the cache
|
||||
(when (nil? (preload-cache-get _preload-cache url))
|
||||
@@ -1148,7 +1148,7 @@
|
||||
(define VERB_SELECTOR
|
||||
(str "[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]"))
|
||||
|
||||
(define process-elements
|
||||
(define process-elements :effects [mutation io]
|
||||
(fn (root)
|
||||
;; Find all elements with sx-* verb attributes and process them.
|
||||
(let ((els (dom-query-all (or root (dom-body)) VERB_SELECTOR)))
|
||||
@@ -1165,7 +1165,7 @@
|
||||
(process-emit-elements root)))
|
||||
|
||||
|
||||
(define process-one
|
||||
(define process-one :effects [mutation io]
|
||||
(fn (el)
|
||||
;; Process a single element with an sx-* verb attribute
|
||||
(let ((verb-info (get-verb-info el)))
|
||||
@@ -1193,7 +1193,7 @@
|
||||
;; On click → dispatches CustomEvent "cart:add" with detail {id:42, name:"Widget"}
|
||||
;; The event bubbles up to the island container where bridge-event catches it.
|
||||
|
||||
(define process-emit-elements
|
||||
(define process-emit-elements :effects [mutation io]
|
||||
(fn (root)
|
||||
(let ((els (dom-query-all (or root (dom-body)) "[data-sx-emit]")))
|
||||
(for-each
|
||||
@@ -1214,7 +1214,7 @@
|
||||
;; History: popstate handler
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define handle-popstate
|
||||
(define handle-popstate :effects [mutation io]
|
||||
(fn ((scrollY :as number))
|
||||
;; Handle browser back/forward navigation.
|
||||
;; Derive target from [sx-boost] container or fall back to #main-panel.
|
||||
@@ -1241,7 +1241,7 @@
|
||||
;; Initialization
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define engine-init
|
||||
(define engine-init :effects [mutation io]
|
||||
(fn ()
|
||||
;; Initialize: CSS tracking, scripts, hydrate, process.
|
||||
(do
|
||||
|
||||
Reference in New Issue
Block a user