New spec file signals.sx defines the signal runtime: signal, computed, effect, deref, reset!, swap!, batch, dispose, and island scope tracking. eval.sx: defisland special form + island? type predicate in eval-call. boundary.sx: signal primitive declarations (Tier 3). render.sx: defisland in definition-form?. adapter-dom.sx: render-dom-island with reactive context, reactive-text, reactive-attr, reactive-fragment, reactive-list helpers. adapter-html.sx: render-html-island for SSR with data-sx-island/state. adapter-sx.sx: island? handling in wire format serialization. special-forms.sx: defisland declaration with docs and example. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
179 lines
5.3 KiB
Plaintext
179 lines
5.3 KiB
Plaintext
;; ==========================================================================
|
|
;; boundary.sx — SX language boundary contract
|
|
;;
|
|
;; Declares the core I/O primitives that any SX host must provide.
|
|
;; This is the LANGUAGE contract — not deployment-specific.
|
|
;;
|
|
;; Pure primitives (Tier 1) are declared in primitives.sx.
|
|
;; Deployment-specific I/O lives in boundary-app.sx.
|
|
;; Per-service page helpers live in {service}/sx/boundary.sx.
|
|
;;
|
|
;; Format:
|
|
;; (define-io-primitive "name"
|
|
;; :params (param1 param2 &key ...)
|
|
;; :returns "type"
|
|
;; :async true
|
|
;; :doc "description"
|
|
;; :context :request)
|
|
;;
|
|
;; ==========================================================================
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Tier 1: Pure primitives — declared in primitives.sx
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(declare-tier :pure :source "primitives.sx")
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 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?"
|
|
:async true
|
|
:doc "Current authenticated user dict, or nil."
|
|
:context :request)
|
|
|
|
(define-io-primitive "request-arg"
|
|
:params (name &rest default)
|
|
:returns "any"
|
|
:async true
|
|
:doc "Read a query string argument from the current request."
|
|
:context :request)
|
|
|
|
(define-io-primitive "request-path"
|
|
:params ()
|
|
:returns "string"
|
|
:async true
|
|
:doc "Current request path."
|
|
:context :request)
|
|
|
|
(define-io-primitive "request-view-args"
|
|
:params (key)
|
|
:returns "any"
|
|
:async true
|
|
:doc "Read a URL view argument from the current request."
|
|
:context :request)
|
|
|
|
(define-io-primitive "csrf-token"
|
|
:params ()
|
|
:returns "string"
|
|
:async true
|
|
:doc "Current CSRF token string."
|
|
:context :request)
|
|
|
|
(define-io-primitive "abort"
|
|
:params (status &rest message)
|
|
:returns "nil"
|
|
:async true
|
|
:doc "Raise HTTP error from SX."
|
|
:context :request)
|
|
|
|
;; Routing
|
|
|
|
(define-io-primitive "url-for"
|
|
:params (endpoint &key)
|
|
:returns "string"
|
|
:async true
|
|
:doc "Generate URL for a named endpoint."
|
|
:context :request)
|
|
|
|
(define-io-primitive "route-prefix"
|
|
:params ()
|
|
:returns "string"
|
|
: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"
|
|
: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"
|
|
:async false
|
|
:doc "Versioned static asset URL."
|
|
:context :config)
|
|
|
|
(define-io-primitive "config"
|
|
:params (key)
|
|
:returns "any"
|
|
: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"))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 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"
|
|
:doc "Create a reactive signal container with an initial value.")
|
|
|
|
(declare-signal-primitive "deref"
|
|
:params (signal)
|
|
:returns "any"
|
|
: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"
|
|
:doc "Set a signal to a new value. Notifies all subscribers.")
|
|
|
|
(declare-signal-primitive "swap!"
|
|
:params (signal f &rest args)
|
|
:returns "nil"
|
|
: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"
|
|
: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"
|
|
: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"
|
|
:doc "Group multiple signal writes. Subscribers are notified once at the end,
|
|
after all values have been updated.")
|