;; ========================================================================== ;; boundary-web.sx — Web platform boundary contract ;; ;; I/O primitives, signals, spreads, scopes, and page helpers ;; required by the SX web framework. Built on the core spec. ;; ========================================================================== ;; -------------------------------------------------------------------------- ;; Tier 2: Core I/O primitives — async, side-effectful, need host context ;; ;; These are generic web-platform I/O that any SX web host would provide, ;; regardless of deployment architecture. ;; -------------------------------------------------------------------------- ;; Request context (define-io-primitive "current-user" :params () :returns "dict?" :effects [io] :async true :doc "Current authenticated user dict, or nil." :context :request) (define-io-primitive "request-arg" :params (name &rest default) :returns "any" :effects [io] :async true :doc "Read a query string argument from the current request." :context :request) (define-io-primitive "request-path" :params () :returns "string" :effects [io] :async true :doc "Current request path." :context :request) (define-io-primitive "request-view-args" :params (key) :returns "any" :effects [io] :async true :doc "Read a URL view argument from the current request." :context :request) (define-io-primitive "csrf-token" :params () :returns "string" :effects [io] :async true :doc "Current CSRF token string." :context :request) (define-io-primitive "abort" :params (status &rest message) :returns "nil" :effects [io] :async true :doc "Raise HTTP error from SX." :context :request) ;; Routing (define-io-primitive "url-for" :params (endpoint &key) :returns "string" :effects [io] :async true :doc "Generate URL for a named endpoint." :context :request) (define-io-primitive "route-prefix" :params () :returns "string" :effects [io] :async true :doc "Service URL prefix for dev/prod routing." :context :request) ;; Config and host context (sync — no await needed) (define-io-primitive "app-url" :params (service &rest path) :returns "string" :effects [io] :async false :doc "Full URL for a service: (app-url \"blog\" \"/my-post/\")." :context :config) (define-io-primitive "asset-url" :params (&rest path) :returns "string" :effects [io] :async false :doc "Versioned static asset URL." :context :config) (define-io-primitive "config" :params (key) :returns "any" :effects [io] :async false :doc "Read a value from host configuration." :context :config) ;; -------------------------------------------------------------------------- ;; Boundary types — what's allowed to cross the host-SX boundary ;; -------------------------------------------------------------------------- (define-boundary-types (list "number" "string" "boolean" "nil" "keyword" "list" "dict" "sx-source")) ;; -------------------------------------------------------------------------- ;; Web interop — reading non-SX request formats ;; ;; SX's native wire format is SX (text/sx). These primitives bridge to ;; legacy web formats: HTML form encoding, JSON bodies, HTTP headers. ;; They're useful for interop but not fundamental to SX-to-SX communication. ;; -------------------------------------------------------------------------- (define-io-primitive "now" :params (&rest format) :returns "string" :async true :doc "Current timestamp. Optional format string (strftime). Default ISO 8601." :context :request) (define-io-primitive "sleep" :params (ms) :returns "nil" :async true :doc "Pause execution for ms milliseconds. For demos and testing." :context :request) (define-io-primitive "request-form" :params (name &rest default) :returns "any" :async true :doc "Read a form field from a POST/PUT/PATCH request body." :context :request) (define-io-primitive "request-json" :params () :returns "dict?" :async true :doc "Read JSON body from the current request, or nil if not JSON." :context :request) (define-io-primitive "request-header" :params (name &rest default) :returns "string?" :async true :doc "Read a request header value by name." :context :request) (define-io-primitive "request-content-type" :params () :returns "string?" :async true :doc "Content-Type of the current request." :context :request) (define-io-primitive "request-args-all" :params () :returns "dict" :async true :doc "All query string parameters as a dict." :context :request) (define-io-primitive "request-form-all" :params () :returns "dict" :async true :doc "All form fields as a dict." :context :request) (define-io-primitive "request-form-list" :params (field-name) :returns "list" :async true :doc "All values for a multi-value form field as a list." :context :request) (define-io-primitive "request-headers-all" :params () :returns "dict" :async true :doc "All request headers as a dict (lowercase keys)." :context :request) (define-io-primitive "request-file-name" :params (field-name) :returns "string?" :async true :doc "Filename of an uploaded file by field name, or nil." :context :request) ;; Response manipulation (define-io-primitive "set-response-header" :params (name value) :returns "nil" :async true :doc "Set a response header. Applied after handler returns." :context :request) (define-io-primitive "set-response-status" :params (status) :returns "nil" :async true :doc "Set the HTTP response status code. Applied after handler returns." :context :request) ;; Ephemeral state — per-process, resets on restart (define-io-primitive "state-get" :params (key &rest default) :returns "any" :async true :doc "Read from ephemeral per-process state dict." :context :request) (define-io-primitive "state-set!" :params (key value) :returns "nil" :async true :doc "Write to ephemeral per-process state dict." :context :request) ;; -------------------------------------------------------------------------- ;; Tier 3: Signal primitives — reactive state for islands ;; ;; These are pure primitives (no IO) but are separated from primitives.sx ;; because they introduce a new type (signal) and depend on signals.sx. ;; -------------------------------------------------------------------------- (declare-tier :signals :source "signals.sx") (declare-signal-primitive "signal" :params (initial-value) :returns "signal" :effects [] :doc "Create a reactive signal container with an initial value.") (declare-signal-primitive "deref" :params (signal) :returns "any" :effects [] :doc "Read a signal's current value. In a reactive context (inside an island), subscribes the current DOM binding to the signal. Outside reactive context, just returns the value.") (declare-signal-primitive "reset!" :params (signal value) :returns "nil" :effects [mutation] :doc "Set a signal to a new value. Notifies all subscribers.") (declare-signal-primitive "swap!" :params (signal f &rest args) :returns "nil" :effects [mutation] :doc "Update a signal by applying f to its current value. (swap! s inc) is equivalent to (reset! s (inc (deref s))) but atomic.") (declare-signal-primitive "computed" :params (compute-fn) :returns "signal" :effects [] :doc "Create a derived signal that recomputes when its dependencies change. Dependencies are discovered automatically by tracking deref calls.") (declare-signal-primitive "effect" :params (effect-fn) :returns "lambda" :effects [mutation] :doc "Run a side effect that re-runs when its signal dependencies change. Returns a dispose function. If the effect function returns a function, it is called as cleanup before the next run.") (declare-signal-primitive "batch" :params (thunk) :returns "any" :effects [mutation] :doc "Group multiple signal writes. Subscribers are notified once at the end, after all values have been updated.") ;; -------------------------------------------------------------------------- ;; Tier 4: Spread + Collect — render-time attribute injection and accumulation ;; ;; `spread` is a new type: a dict of attributes that, when returned as a child ;; of an HTML element, merges its attrs onto the parent element rather than ;; rendering as content. This enables components like `~cssx/tw` to inject ;; classes and styles onto their parent from inside the child list. ;; ;; `collect!` / `collected` are render-time accumulators. Values are collected ;; into named buckets (with deduplication) during rendering and retrieved at ;; flush points (e.g. a single