Files
rose-ash/sx/sx/applications/graphql/executor/index.sx
giles fc24cc704d GraphQL: query/mutation/fragments/vars/executor + parser spec + tests
New graphql application. 676-line test-graphql.sx covers parser, executor,
fetch-gql integration. lib/graphql.sx (686L) is the core parser/AST;
lib/graphql-exec.sx (219L) runs resolvers. applications/graphql/spec.sx
declares the application. sx/sx/applications/graphql/ provides the doc
pages (parser, queries, mutation, fragments, vars, fetch-gql, executor).

Includes rebuilt sx_browser.bc.js / sx_browser.bc.wasm.js bundles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:08:00 +00:00

92 lines
5.2 KiB
Plaintext

;; GraphQL: executor — live execute against /api.execute-demo
(defcomp
()
(~docs/page
:title "GraphQL · Executor"
(p
(~tw :tokens "text-lg text-gray-600 mb-2")
"The executor in "
(code "lib/graphql-exec.sx")
" walks the parsed AST, dispatches root fields to a resolver, "
"and projects each result down to the selected fields.")
(p
(~tw :tokens "text-gray-500 mb-6")
"The endpoint below uses a static seed dataset (users, posts, comments) "
"and a resolver written in plain SX. The same bytecode runs the docs site test suite.")
(~docs/section
:title "Seed data"
:id "seed"
(p
(~tw :tokens "text-gray-600 mb-3")
"Three tables, hardcoded in the handler file. No database required — "
"every query and mutation hits this in-memory store.")
(~docs/code
:src "(define gql-seed-users\n (list\n {:id 1 :name \"Alice\" :email \"alice@test.com\" :role \"admin\"}\n {:id 2 :name \"Bob\" :email \"bob@test.com\" :role \"user\"}\n {:id 3 :name \"Carol\" :email \"carol@test.com\" :role \"user\"}))\n\n(define gql-seed-posts\n (list\n {:id 101 :authorId 1 :title \"Hello, world\" :body \"First post in SX GraphQL.\"}\n {:id 102 :authorId 2 :title \"GraphQL in SX\" :body \"Every op compiles to bytecode.\"}\n {:id 103 :authorId 1 :title \"Quiet night\" :body \"Just thinking about parsers.\"}))\n\n(define gql-seed-comments\n (list\n {:id 1001 :postId 101 :authorId 2 :text \"Nice one.\"}\n {:id 1002 :postId 102 :authorId 3 :text \"Compiled to bytecode?!\"}\n {:id 1003 :postId 102 :authorId 1 :text \"Yes — every op is CEK.\"}))"))
(~docs/section
:title "Resolver"
:id "resolver"
(p
(~tw :tokens "text-gray-600 mb-3")
"The resolver receives "
(code "(field-name args-dict op-type)")
" per root field. Return a scalar, a dict, or a list of dicts — "
"the executor projects it down to the requested selection set.")
(~docs/code
:src "(define gql-seed-resolve\n (fn (field-name args op-type)\n (cond\n ((= field-name \"user\")\n (gql-find-by-id gql-seed-users (get args :id)))\n ((= field-name \"users\") gql-seed-users)\n ((= field-name \"post\")\n (gql-find-by-id gql-seed-posts (get args :id)))\n ((= field-name \"posts\")\n (let ((author-id (get args :authorId)))\n (if author-id\n (gql-filter-by gql-seed-posts :authorId author-id)\n gql-seed-posts)))\n ((= field-name \"comments\")\n (let ((post-id (get args :postId)))\n (if post-id\n (gql-filter-by gql-seed-comments :postId post-id)\n gql-seed-comments)))\n ((= field-name \"echo\") (get args :message))\n ((= field-name \"createPost\")\n {:id 999\n :title (get args :title)\n :body (get args :body)\n :authorId (get args :authorId)})\n ((= field-name \"likePost\")\n {:id (get args :id) :liked true})\n (true nil))))"))
(~docs/section
:title "Live endpoint"
:id "endpoint"
(p
(~tw :tokens "text-gray-600 mb-3")
"POST "
(code "/sx/(applications.(graphql.(api.execute-demo)))")
" with form fields "
(code "query")
" and optional "
(code "variables")
" (an SX dict literal like "
(code "{:id 1}")
").")
(~docs/code
:src "(defhandler\n gql-execute-demo\n :path \"/sx/(applications.(graphql.(api.execute-demo)))\"\n :method :post\n :csrf false\n :returns \"text\"\n (&key query variables)\n (let ((doc (gql-parse query))\n (vars-dict (gql-parse-vars variables)))\n (let ((result (gql-execute doc vars-dict gql-seed-resolve)))\n (str\n \"<pre>\" (escape (sx-serialize vars-dict)) \"</pre>\"\n \"<pre>\" (escape (sx-serialize result)) \"</pre>\")))))"))
(~docs/section
:title "Try it"
:id "try"
(p
(~tw :tokens "text-gray-600 mb-3")
"The query is sent straight to the handler; the result is projected to your selection set.")
(form
(~tw :tokens "space-y-3")
:hx-post "/sx/(applications.(graphql.(api.execute-demo)))"
:hx-target "#exec-out"
:hx-swap "innerHTML"
(label
(~tw :tokens "block text-sm font-medium text-gray-700")
"Query")
(textarea
:name "query"
:rows "7"
(~tw
:tokens "w-full font-mono text-sm p-3 border border-gray-300 rounded-lg")
"query Q($id: ID!) {\n post(id: $id) {\n title\n body\n authorId\n }\n}")
(label
(~tw :tokens "block text-sm font-medium text-gray-700")
"Variables")
(textarea
:name "variables"
:rows "2"
(~tw
:tokens "w-full font-mono text-sm p-3 border border-gray-300 rounded-lg")
"{:id 102}")
(button
:type "submit"
(~tw
:tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700")
"Execute"))
(div
:id "exec-out"
(~tw :tokens "mt-4")
(span
(~tw :tokens "text-gray-400 text-sm")
"Variables + result will appear here.")))))