sx-host plan steps 1-2: defhelper + SX config + SXTP spec + nav tools
Step 1 — defhelper: SX-defined page data helpers replace Python helpers. (defhelper name (params) body) in .sx files, using existing IO primitives (query, action, service). Loaded into OCaml kernel as pure SX defines. Step 2 — SX config: app-config.sx replaces app-config.yaml with (defconfig) form. (env-get "VAR") resolves secrets from environment. Kebab-to-underscore aliasing ensures backward compatibility with all 174 config consumers. Also: SXTP protocol spec (applications/sxtp/spec.sx), docs article, sx_nav move/delete modes, reactive-runtime moved to geography. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -142,25 +142,25 @@
|
||||
(define
|
||||
reactive-runtime-nav-items
|
||||
(list
|
||||
(dict :label "Ref" :href "/sx/(applications.(reactive-runtime.ref))")
|
||||
(dict :label "Ref" :href "/sx/(geography.(reactive-runtime.ref))")
|
||||
(dict
|
||||
:label "Foreign FFI"
|
||||
:href "/sx/(applications.(reactive-runtime.foreign))")
|
||||
:href "/sx/(geography.(reactive-runtime.foreign))")
|
||||
(dict
|
||||
:label "State Machines"
|
||||
:href "/sx/(applications.(reactive-runtime.machine))")
|
||||
:href "/sx/(geography.(reactive-runtime.machine))")
|
||||
(dict
|
||||
:label "Commands"
|
||||
:href "/sx/(applications.(reactive-runtime.commands))")
|
||||
:href "/sx/(geography.(reactive-runtime.commands))")
|
||||
(dict
|
||||
:label "Render Loop"
|
||||
:href "/sx/(applications.(reactive-runtime.loop))")
|
||||
:href "/sx/(geography.(reactive-runtime.loop))")
|
||||
(dict
|
||||
:label "Keyed Lists"
|
||||
:href "/sx/(applications.(reactive-runtime.keyed-lists))")
|
||||
:href "/sx/(geography.(reactive-runtime.keyed-lists))")
|
||||
(dict
|
||||
:label "App Shell"
|
||||
:href "/sx/(applications.(reactive-runtime.app-shell))")))
|
||||
:href "/sx/(geography.(reactive-runtime.app-shell))")))
|
||||
|
||||
(define
|
||||
native-browser-nav-items
|
||||
@@ -739,7 +739,7 @@
|
||||
:select-colours "aria-selected:bg-violet-200 aria-selected:text-violet-900"))
|
||||
items)))
|
||||
|
||||
(define sx-nav-tree {:href "/sx/" :children (list {:href "/sx/(geography)" :children (list {:href "/sx/(geography.(reactive))" :children reactive-islands-nav-items :label "Reactive Islands"} {:href "/sx/(geography.(hypermedia))" :children (list {:href "/sx/(geography.(hypermedia.(reference)))" :children reference-nav-items :label "Reference"} {:href "/sx/(geography.(hypermedia.(example)))" :children examples-nav-items :label "Examples"}) :label "Hypermedia Lakes"} {:href "/sx/(geography.(scopes))" :summary "The unified primitive beneath provide, collect!, spreads, and islands. Named scope with downward value, upward accumulation, and a dedup flag." :label "Scopes"} {:href "/sx/(geography.(provide))" :summary "Sugar for scope-with-value. Render-time dynamic scope — the substrate beneath spreads, CSSX, and script collection." :label "Provide / Emit!"} {:href "/sx/(geography.(spreads))" :summary "Child-to-parent communication across render boundaries — spread, collect!, reactive-spread, built on scopes." :label "Spreads"} {:href "/sx/(geography.(marshes))" :children marshes-examples-nav-items :summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted." :label "Marshes"} {:href "/sx/(geography.(isomorphism))" :children isomorphism-nav-items :label "Isomorphism"} {:href "/sx/(geography.(cek))" :children cek-nav-items :label "CEK Machine"}) :label "Geography"} {:href "/sx/(language)" :children (list {:href "/sx/(language.(doc))" :children docs-nav-items :label "Docs"} {:href "/sx/(language.(spec))" :children specs-nav-items :label "Specs"} {:href "/sx/(language.(spec.(explore.evaluator)))" :label "Spec Explorer"} {:href "/sx/(language.(bootstrapper))" :children bootstrappers-nav-items :label "Bootstrappers"} {:href "/sx/(language.(test))" :children testing-nav-items :label "Testing"}) :label "Language"} {:href "/sx/(applications)" :children (list {:href "/sx/(applications.(sx-urls))" :label "SX URLs"} {:href "/sx/(applications.(cssx))" :children cssx-nav-items :label "CSSX"} {:href "/sx/(applications.(protocol))" :children protocols-nav-items :label "Protocols"} {:href "/sx/(applications.(sx-pub))" :label "sx-pub"} {:href "/sx/(applications.(sx-tools))" :label "SX Tools"} {:href "/sx/(applications.(reactive-runtime))" :children reactive-runtime-nav-items :label "Reactive Runtime"}) :label "Applications"} {:href "/sx/(etc)" :children (list {:href "/sx/(etc.(essay))" :children essays-nav-items :label "Essays"} {:href "/sx/(etc.(philosophy))" :children philosophy-nav-items :label "Philosophy"} {:href "/sx/(etc.(plan))" :children plans-nav-items :label "Plans"}) :label "Etc"}) :label "sx"})
|
||||
(define sx-nav-tree {:href "/sx/" :children (list {:href "/sx/(geography)" :children (list {:href "/sx/(geography.(reactive))" :children reactive-islands-nav-items :label "Reactive Islands"} {:href "/sx/(geography.(hypermedia))" :children (list {:href "/sx/(geography.(hypermedia.(reference)))" :children reference-nav-items :label "Reference"} {:href "/sx/(geography.(hypermedia.(example)))" :children examples-nav-items :label "Examples"}) :label "Hypermedia Lakes"} {:href "/sx/(geography.(scopes))" :summary "The unified primitive beneath provide, collect!, spreads, and islands. Named scope with downward value, upward accumulation, and a dedup flag." :label "Scopes"} {:href "/sx/(geography.(provide))" :summary "Sugar for scope-with-value. Render-time dynamic scope — the substrate beneath spreads, CSSX, and script collection." :label "Provide / Emit!"} {:href "/sx/(geography.(spreads))" :summary "Child-to-parent communication across render boundaries — spread, collect!, reactive-spread, built on scopes." :label "Spreads"} {:href "/sx/(geography.(marshes))" :children marshes-examples-nav-items :summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted." :label "Marshes"} {:href "/sx/(geography.(isomorphism))" :children isomorphism-nav-items :label "Isomorphism"} {:href "/sx/(geography.(cek))" :children cek-nav-items :label "CEK Machine"}) :label "Geography"} {:href "/sx/(language)" :children (list {:href "/sx/(language.(doc))" :children docs-nav-items :label "Docs"} {:href "/sx/(language.(spec))" :children specs-nav-items :label "Specs"} {:href "/sx/(language.(spec.(explore.evaluator)))" :label "Spec Explorer"} {:href "/sx/(language.(bootstrapper))" :children bootstrappers-nav-items :label "Bootstrappers"} {:href "/sx/(language.(test))" :children testing-nav-items :label "Testing"}) :label "Language"} {:href "/sx/(applications)" :children (list {:href "/sx/(applications.(sx-urls))" :label "SX URLs"} {:href "/sx/(applications.(cssx))" :children cssx-nav-items :label "CSSX"} {:href "/sx/(applications.(protocol))" :children protocols-nav-items :label "Protocols"} {:href "/sx/(applications.(sx-pub))" :label "sx-pub"} {:href "/sx/(applications.(sx-tools))" :label "SX Tools"} {:href "/sx/(geography.(reactive-runtime))" :children reactive-runtime-nav-items :label "Reactive Runtime"}) :label "Applications"} {:href "/sx/(etc)" :children (list {:href "/sx/(etc.(essay))" :children essays-nav-items :label "Essays"} {:href "/sx/(etc.(philosophy))" :children philosophy-nav-items :label "Philosophy"} {:href "/sx/(etc.(plan))" :children plans-nav-items :label "Plans"}) :label "Etc"}) :label "sx"})
|
||||
|
||||
(define
|
||||
has-descendant-href?
|
||||
@@ -811,3 +811,6 @@
|
||||
i
|
||||
(find-loop (+ i 1))))))
|
||||
(find-loop 0))))
|
||||
|
||||
(define sxtp-nav-items
|
||||
(list (dict :label "SXTP Protocol" :href "/sx/(applications.(sxtp))")))
|
||||
|
||||
@@ -77,7 +77,11 @@
|
||||
:href "/sx/(geography.(cek))"
|
||||
:label "CEK Machine"
|
||||
:children cek-nav-items)
|
||||
(dict :href "/sx/(geography.(capabilities))" :label "Capabilities")))
|
||||
(dict :href "/sx/(geography.(capabilities))" :label "Capabilities")
|
||||
(dict
|
||||
:href "/sx/(geography.(reactive-runtime))"
|
||||
:label "Reactive Runtime"
|
||||
:children reactive-runtime-nav-items)))
|
||||
(dict
|
||||
:href "/sx/(language)"
|
||||
:label "Language"
|
||||
@@ -115,13 +119,10 @@
|
||||
:label "Protocols"
|
||||
:children protocols-nav-items)
|
||||
(dict :href "/sx/(applications.(sx-pub))" :label "sx-pub")
|
||||
(dict
|
||||
:href "/sx/(applications.(reactive-runtime))"
|
||||
:label "Reactive Runtime"
|
||||
:children reactive-runtime-nav-items)
|
||||
(dict
|
||||
:href "/sx/(applications.(native-browser))"
|
||||
:label "Native Browser")))
|
||||
:label "Native Browser")
|
||||
(dict :href "/sx/(applications.(sxtp))" :label "SXTP Protocol")))
|
||||
(dict :href "/sx/(tools)" :label "Tools" :children tools-nav-items)
|
||||
(dict
|
||||
:href "/sx/(etc)"
|
||||
|
||||
@@ -674,3 +674,5 @@
|
||||
|
||||
(define eval-rules (fn (&key title &rest args) (quasiquote (~geography/eval-rules-content))))
|
||||
|
||||
|
||||
(define sxtp (make-page-fn "~applications/sxtp/content" "~applications/sxtp/" nil "-content"))
|
||||
|
||||
340
sx/sx/sxtp.sx
Normal file
340
sx/sx/sxtp.sx
Normal file
@@ -0,0 +1,340 @@
|
||||
(defcomp
|
||||
~applications/sxtp/content
|
||||
()
|
||||
(~docs/page
|
||||
:title "SXTP Protocol"
|
||||
(~docs/section
|
||||
:title "Overview"
|
||||
:id "overview"
|
||||
(p
|
||||
"SXTP — SX Transfer Protocol — is HTTP reimagined where the wire format "
|
||||
(em "is")
|
||||
" the language. Requests, responses, headers, cookies, status conditions, and bodies are all s-expressions. There is no text framing, no content-type negotiation, no URL query-string encoding.")
|
||||
(p "Design principles:")
|
||||
(ul
|
||||
:class "list-disc list-inside space-y-2 mt-2"
|
||||
(li
|
||||
(strong "SX all the way")
|
||||
" — every datum on the wire is a valid SX value")
|
||||
(li
|
||||
(strong "Open verb set")
|
||||
" — any symbol is a legal verb, not just GET/POST/PUT/DELETE")
|
||||
(li
|
||||
(strong "Structured metadata")
|
||||
" — headers and cookies are dicts, not flat strings")
|
||||
(li
|
||||
(strong "Capability-scoped")
|
||||
" — requests declare required capabilities")
|
||||
(li
|
||||
(strong "Content-addressed")
|
||||
" — responses can be cached by hash")
|
||||
(li
|
||||
(strong "Streamable")
|
||||
" — chunked responses are sequences of expressions")))
|
||||
(~docs/section
|
||||
:title "Requests"
|
||||
:id "requests"
|
||||
(p
|
||||
"A request is a list beginning with the symbol "
|
||||
(code "request")
|
||||
". All fields are keyword arguments.")
|
||||
(~docs/code
|
||||
:src (highlight "(request :verb navigate :path \"/\")" "lisp"))
|
||||
(p "Full request with all fields:")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
"(request\n :verb navigate\n :path \"/geography/capabilities\"\n :headers {:accept \"text/sx\" :language \"en\"}\n :cookies {:session \"tok_abc123\" :prefs {:theme \"dark\"}}\n :params {:page 1 :per-page 20}\n :capabilities (fetch query)\n :body nil)"
|
||||
"lisp"))
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200 mt-4"
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Field")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Description")))
|
||||
(tbody
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" ":verb")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Symbol — the action to perform (required)"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" ":path")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"String — resource path (required)"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" ":headers")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Dict — structured request metadata"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" ":cookies")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Dict — client state, values can be any SX type"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" ":params")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Dict — query parameters as typed values"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" ":capabilities")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"List — capabilities this request requires"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" ":body")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Any SX value — request payload"))))))
|
||||
(~docs/section
|
||||
:title "Responses"
|
||||
:id "responses"
|
||||
(p
|
||||
"A response is a list beginning with the symbol "
|
||||
(code "response")
|
||||
".")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
"(response :status ok\n :headers {:content-type \"text/sx\" :cache :immutable}\n :set-cookie {:session {:value \"tok_xyz\" :max-age 3600 :path \"/\"}}\n :body (page :title \"Home\" (h1 \"Welcome\")))"
|
||||
"lisp"))
|
||||
(p
|
||||
"The body isn't serialized HTML that needs parsing — it's a live component tree the browser evaluates directly."))
|
||||
(~docs/section
|
||||
:title "Verbs"
|
||||
:id "verbs"
|
||||
(p
|
||||
"Unlike HTTP's fixed set, any symbol is a valid verb. Convention defines common verbs; domains add their own.")
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200 mt-4"
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Verb")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "Purpose")))
|
||||
(tbody
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" "navigate")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Retrieve a page for display — analogous to GET for documents"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" "fetch")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Retrieve data — analogous to GET for APIs"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" "query")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Structured query — body contains a query expression"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" "mutate")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Change state — analogous to POST/PUT/PATCH"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" "create")
|
||||
(td :class "px-3 py-2 text-stone-600" "Create a new resource"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" "delete")
|
||||
(td :class "px-3 py-2 text-stone-600" "Remove a resource"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" "subscribe")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Open a streaming channel for real-time updates"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" "inspect")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Retrieve metadata about a resource (capabilities, schema)"))
|
||||
(tr
|
||||
(td :class "px-3 py-2 text-stone-700 font-mono" "ping")
|
||||
(td :class "px-3 py-2 text-stone-600" "Liveness check")))))
|
||||
(p :class "mt-4" "Domains define their own verbs freely:")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
"(request :verb publish :path \"/blog/draft-123\")\n(request :verb checkout :path \"/cart\")\n(request :verb render :path \"/artdag/node/abc\" :params {:format \"png\"})\n(request :verb federate :path \"/outbox\" :body (activity ...))"
|
||||
"lisp")))
|
||||
(~docs/section
|
||||
:title "What HTTP got wrong"
|
||||
:id "http-comparison"
|
||||
(div
|
||||
:class "overflow-x-auto rounded border border-stone-200"
|
||||
(table
|
||||
:class "w-full text-left text-sm"
|
||||
(thead
|
||||
(tr
|
||||
:class "border-b border-stone-200 bg-stone-100"
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "HTTP pain")
|
||||
(th :class "px-3 py-2 font-medium text-stone-600" "SXTP answer")))
|
||||
(tbody
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
"Fixed verb set (GET/POST/PUT/DELETE)")
|
||||
(td :class "px-3 py-2 text-stone-600" "Any symbol is a verb"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
"Headers are flat string pairs")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Headers are dicts — nested, typed"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
"Cookies are encoded strings")
|
||||
(td :class "px-3 py-2 text-stone-600" "Cookies are SX values"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
"Body requires content-type negotiation")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Body is always SX — rendering is the client's job"))
|
||||
(tr
|
||||
:class "border-b border-stone-100"
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
"URL query strings (?a=1&b=2)")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Params are part of the request expression"))
|
||||
(tr
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-700"
|
||||
"Separate mechanisms for streaming")
|
||||
(td
|
||||
:class "px-3 py-2 text-stone-600"
|
||||
"Streaming is just :stream true + chunk sequences"))))))
|
||||
(~docs/section
|
||||
:title "Status and conditions"
|
||||
:id "status"
|
||||
(p
|
||||
"Status is a symbol, not a number. Conditions replace error codes with structured, informative values.")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
"(response :status not-found\n :body (condition :type resource-not-found\n :path \"/blog/nonexistent\"\n :message \"No such post\"\n :retry false))"
|
||||
"lisp"))
|
||||
(p "Conditions are extensible — domains define their own:")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
"(condition :type payment-declined\n :reason :insufficient-funds\n :provider \"sumup\")"
|
||||
"lisp")))
|
||||
(~docs/section
|
||||
:title "Streaming"
|
||||
:id "streaming"
|
||||
(p
|
||||
"A streaming response sets "
|
||||
(code ":stream true")
|
||||
". The body becomes a sequence of chunk expressions.")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
";; Ordered chunks\n(response :status ok :stream true)\n(chunk :seq 0 :body (tr (td \"Row 1\") (td \"data\")))\n(chunk :seq 1 :body (tr (td \"Row 2\") (td \"data\")))\n(chunk :done true)\n\n;; Server-sent events via subscribe\n(request :verb subscribe :path \"/events/live\")\n\n(event :type new-event :id \"evt-42\"\n :body (div :class \"event-card\" (h3 \"Jazz Night\")))\n(event :type update :id \"evt-42\"\n :body {:attendees 51})\n(event :type heartbeat :time 1711612800)"
|
||||
"lisp")))
|
||||
(~docs/section
|
||||
:title "Capabilities"
|
||||
:id "capabilities"
|
||||
(p
|
||||
"Requests declare the capabilities they need. The server checks these against the session's granted capabilities. Insufficient capabilities produce "
|
||||
(code "(response :status forbidden)")
|
||||
".")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
";; Client declares\n(request :verb query :path \"/events\"\n :capabilities (fetch db:read))\n\n;; Server grants on auth\n(response :status ok\n :set-cookie {:capabilities {:value (fetch query db:read mutate)\n :max-age 86400\n :secure true}})"
|
||||
"lisp"))
|
||||
(p "Inspect what a resource requires:")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
"(request :verb inspect :path \"/cart/checkout\")\n\n(response :status ok\n :body {:required-capabilities (mutate cart:checkout)\n :available-verbs (inspect mutate)\n :params-schema {:shipping-address \"dict\"\n :payment-method \"symbol\"}})"
|
||||
"lisp")))
|
||||
(~docs/section
|
||||
:title "Caching"
|
||||
:id "caching"
|
||||
(p
|
||||
"Content-addressed caching. The response hash "
|
||||
(em "is")
|
||||
" the cache key. No ETags, no Last-Modified — just SX content hashes.")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
";; Server provides hash\n(response :status ok\n :headers {:content-hash \"sha3-abc123...\"\n :cache :immutable}\n :body ...)\n\n;; Client validates\n(request :verb fetch :path \"/geography/capabilities\"\n :headers {:if-match \"sha3-abc123...\"})\n\n(response :status not-modified)"
|
||||
"lisp"))
|
||||
(p
|
||||
"Three cache policies: "
|
||||
(code ":immutable")
|
||||
" (content-addressed, never changes), "
|
||||
(code ":revalidate")
|
||||
" (check hash before using), "
|
||||
(code ":none")
|
||||
" (dynamic content)."))
|
||||
(~docs/section
|
||||
:title "Wire format"
|
||||
:id "wire-format"
|
||||
(p
|
||||
"On the wire, each message is a length-prefixed SX expression. Length is a decimal integer as ASCII, followed by newline. The SX expression is UTF-8 encoded.")
|
||||
(~docs/code
|
||||
:src (highlight "43\n(request :verb ping :path \"/\" :body nil)" "text"))
|
||||
(p
|
||||
"Connections are persistent — multiple request/response pairs on the same connection. Pipelining is allowed. TLS is the transport security layer: "
|
||||
(code "sxtp://")
|
||||
" is plaintext (port 5380), "
|
||||
(code "sxtps://")
|
||||
" is TLS (port 5381)."))
|
||||
(~docs/section
|
||||
:title "URI scheme"
|
||||
:id "uri"
|
||||
(p "The browser translates URIs into request expressions:")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
"sxtps://blog.rose-ash.com/geography/capabilities\n\n;; becomes\n\n(request :verb navigate\n :path \"/geography/capabilities\"\n :headers {:host \"blog.rose-ash.com\"})"
|
||||
"lisp")))
|
||||
(~docs/section
|
||||
:title "Examples"
|
||||
:id "examples"
|
||||
(p "Page navigation:")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
"(request :verb navigate :path \"/geography/capabilities\"\n :headers {:host \"sx.rose-ash.com\" :accept \"text/sx\"})\n\n(response :status ok\n :headers {:content-type \"text/sx\"\n :content-hash \"sha3-9f2a...\"}\n :body (page :title \"Capabilities\"\n (h1 \"Geography Capabilities\")\n (~capability-list :domain \"geography\")))"
|
||||
"lisp"))
|
||||
(p "Structured query:")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
"(request :verb query :path \"/events\"\n :capabilities (fetch db:read)\n :params {:after \"2026-03-01\" :limit 10}\n :body (filter (events) (fn (e) (> (:attendees e) 50))))\n\n(response :status ok\n :headers {:cache :revalidate}\n :body ((event :id \"evt-42\" :title \"Jazz Night\" :attendees 87)\n (event :id \"evt-55\" :title \"Art Walk\" :attendees 120)))"
|
||||
"lisp"))
|
||||
(p "Creating a resource:")
|
||||
(~docs/code
|
||||
:src (highlight
|
||||
"(request :verb create :path \"/blog/posts\"\n :capabilities (mutate blog:publish)\n :cookies {:session \"tok_abc123\"}\n :body {:title \"SXTP Protocol\"\n :body (article (h1 \"SXTP\") (p \"Everything is SX.\"))\n :tags (\"protocol\" \"sx\" \"web\")})\n\n(response :status created\n :headers {:location \"/blog/posts/sxtp-protocol\"\n :content-hash \"sha3-ff01...\"}\n :body {:id \"post-789\"\n :path \"/blog/posts/sxtp-protocol\"\n :created-at 1711612800})"
|
||||
"lisp")))
|
||||
(~docs/section
|
||||
:title "Specification"
|
||||
:id "spec"
|
||||
(p
|
||||
"The formal specification lives in "
|
||||
(code "applications/sxtp/spec.sx")
|
||||
" — a self-describing SX file where the field definitions are themselves SX data structures that the protocol can introspect."))))
|
||||
Reference in New Issue
Block a user