Loopback remote implementations for headless Fulcro applications.
Provides synchronous remote implementations that execute locally (loopback) instead of making network requests:
sync-remote - Calls a handler function directlypathom-remote - Integrates with Pathom parsersring-remote - Simple Ring handler integrationfulcro-ring-remote - Full Ring middleware integration with Fulcro's transit encodingmock-remote - Returns canned responses for testingLoopback remote implementations for headless Fulcro applications. Provides synchronous remote implementations that execute locally (loopback) instead of making network requests: - `sync-remote` - Calls a handler function directly - `pathom-remote` - Integrates with Pathom parsers - `ring-remote` - Simple Ring handler integration - `fulcro-ring-remote` - Full Ring middleware integration with Fulcro's transit encoding - `mock-remote` - Returns canned responses for testing
(delayed-remote delegate)Create a remote that simulates a delayed response.
Useful for testing loading states and timeouts.
Options:
Example:
(def remote (delayed-remote (sync-remote handler))
;; do network ops with fulcro
(loopback-remotes/deliver-results! remote)
Create a remote that simulates a delayed response. Useful for testing loading states and timeouts. Options: - :delegate - Remote to delegate to (required) Example: ```clojure (def remote (delayed-remote (sync-remote handler)) ;; do network ops with fulcro (loopback-remotes/deliver-results! remote) ```
(deliver-results! delayed-remote)Delivers all of the delayed results on a delayed remote synchronously and immediately. In order of original submission.
Delivers all of the delayed results on a delayed remote synchronously and immediately. In order of original submission.
(failing-remote &
{:keys [status-code error-message body fail-after delegate]
:or {status-code 500 body {} fail-after 0}})Create a remote that always fails with the given status code.
Useful for testing error handling.
Options:
Example:
;; Always fail
(failing-remote :status-code 500 :error-message "Server error")
;; Fail after 2 successful requests
(failing-remote
:fail-after 2
:delegate (sync-remote handler))
Create a remote that always fails with the given status code.
Useful for testing error handling.
Options:
- :status-code - HTTP status code to return (default: 500)
- :error-message - Error message to include
- :body - Response body (default: {})
- :fail-after - Number of successful requests before failing (default: 0)
- :delegate - Remote to use for successful requests
Example:
```clojure
;; Always fail
(failing-remote :status-code 500 :error-message "Server error")
;; Fail after 2 successful requests
(failing-remote
:fail-after 2
:delegate (sync-remote handler))
```(fulcro-ring-remote ring-handler
&
{:keys [uri request-middleware response-middleware cookies
session transit-write-handlers
transit-read-handlers]
:or {uri "/api"}})Create a remote that simulates a full Fulcro HTTP remote through a Ring handler.
This provides the same middleware pipeline as the CLJS fulcro-http-remote,
but calls a Ring handler directly instead of making HTTP requests.
ring-handler: A Ring handler function (fn [request] response)
Options:
wrap-fulcro-request. Build chains like:
(-> (wrap-fulcro-request)
(wrap-csrf-token token)
(wrap-custom-headers))wrap-fulcro-response. Build chains like:
(-> (wrap-fulcro-response)
(wrap-error-handling)
(wrap-logging))Example with middleware:
(defn wrap-auth-header [handler token]
(fn [request]
(handler (update request :headers assoc "Authorization" (str "Bearer " token)))))
(fulcro-ring-remote my-ring-app
:uri "/api"
:request-middleware (-> (wrap-fulcro-request)
(wrap-auth-header "my-token"))
:response-middleware (-> (wrap-fulcro-response)
(wrap-error-logging)))
Example simulating cookies:
(fulcro-ring-remote my-ring-app
:cookies {"session" {:value "abc123"}}
:session {:user/id 1})
Create a remote that simulates a full Fulcro HTTP remote through a Ring handler.
This provides the same middleware pipeline as the CLJS `fulcro-http-remote`,
but calls a Ring handler directly instead of making HTTP requests.
ring-handler: A Ring handler function (fn [request] response)
Options:
- :uri - API endpoint URI (default "/api")
- :request-middleware - Client request middleware chain (fn [handler] wrapped-handler)
Defaults to `wrap-fulcro-request`. Build chains like:
(-> (wrap-fulcro-request)
(wrap-csrf-token token)
(wrap-custom-headers))
- :response-middleware - Client response middleware chain (fn [handler] wrapped-handler)
Defaults to `wrap-fulcro-response`. Build chains like:
(-> (wrap-fulcro-response)
(wrap-error-handling)
(wrap-logging))
- :cookies - Initial cookies map to include in requests
- :session - Session data to include in requests
- :transit-write-handlers - Additional transit handlers for encoding
- :transit-read-handlers - Additional transit handlers for decoding
Example with middleware:
```clojure
(defn wrap-auth-header [handler token]
(fn [request]
(handler (update request :headers assoc "Authorization" (str "Bearer " token)))))
(fulcro-ring-remote my-ring-app
:uri "/api"
:request-middleware (-> (wrap-fulcro-request)
(wrap-auth-header "my-token"))
:response-middleware (-> (wrap-fulcro-response)
(wrap-error-logging)))
```
Example simulating cookies:
```clojure
(fulcro-ring-remote my-ring-app
:cookies {"session" {:value "abc123"}}
:session {:user/id 1})
```(mock-remote responses
&
{:keys [default-response simulate-latency-ms on-unmatched]
:or {default-response {}}})Create a remote that returns canned responses.
responses: A map from EQL patterns to response bodies, or a function.
When responses is a map, keys can be:
When responses is a function:
Options:
Example:
(mock-remote
{`my-mutation {:result :success}
[{:users [:user/id :user/name]}] [{:user/id 1 :user/name "John"}]
:default {}})
Create a remote that returns canned responses.
responses: A map from EQL patterns to response bodies, or a function.
When responses is a map, keys can be:
- A complete EQL query/mutation (exact match)
- A mutation symbol (matches any mutation with that symbol)
- :default - Fallback for unmatched requests
When responses is a function:
- (fn [eql] response-body-or-nil)
- Return nil to use default behavior
Options:
- :default-response - Response for unmatched requests (default: {})
- :simulate-latency-ms - Add artificial delay
- :on-unmatched - (fn [eql] response) called for unmatched when no default
Example:
```clojure
(mock-remote
{`my-mutation {:result :success}
[{:users [:user/id :user/name]}] [{:user/id 1 :user/name "John"}]
:default {}})
```(pathom-remote parser & {:keys [env-fn async?]})Create a remote backed by a Pathom parser.
parser: A Pathom parser function with signature: (fn [env eql] result) OR (fn [eql] result) if env-fn is not provided
Options:
Example with Pathom 3:
(pathom-remote
(p/parser {...})
:env-fn (fn [] {:db (get-db-connection)}))
Example with Pathom 2:
(pathom-remote
my-pathom2-parser
:env-fn (fn [] {:request {:session {...}}}))
Create a remote backed by a Pathom parser.
parser: A Pathom parser function with signature:
(fn [env eql] result)
OR (fn [eql] result) if env-fn is not provided
Options:
- :env-fn - (fn [] env) creates parser environment per request.
Called fresh for each request to support request-scoped data.
- :async? - If true, parser returns a core.async channel; blocks via <!!
Example with Pathom 3:
```clojure
(pathom-remote
(p/parser {...})
:env-fn (fn [] {:db (get-db-connection)}))
```
Example with Pathom 2:
```clojure
(pathom-remote
my-pathom2-parser
:env-fn (fn [] {:request {:session {...}}}))
```(recording-remote & {:keys [delegate record-atom] :or {record-atom (atom [])}})Create a remote that records all requests and delegates to another remote.
Useful for verifying what requests were sent during a test.
Options:
Returns: {:remote the-remote :recordings atom-with-recordings}
Each recording is a map with:
Example:
(let [{:keys [remote recordings]} (recording-remote
:delegate (sync-remote handler))]
(set-remote! app :remote remote)
(comp/transact! app [(my-mutation)])
(is (= 1 (count @recordings)))
(is (= 'my-mutation (ffirst (:eql (first @recordings))))))
Create a remote that records all requests and delegates to another remote.
Useful for verifying what requests were sent during a test.
Options:
- :delegate - Remote to delegate actual handling to (required)
- :record-atom - Atom to store recordings (default: creates new atom)
Returns: {:remote the-remote :recordings atom-with-recordings}
Each recording is a map with:
- :eql - The EQL query/mutation
- :timestamp - When the request was made
- :response - The response returned
Example:
```clojure
(let [{:keys [remote recordings]} (recording-remote
:delegate (sync-remote handler))]
(set-remote! app :remote remote)
(comp/transact! app [(my-mutation)])
(is (= 1 (count @recordings)))
(is (= 'my-mutation (ffirst (:eql (first @recordings))))))
```(ring-remote ring-handler
&
{:keys [uri method content-type headers session encode-fn
decode-fn]
:or {uri "/api"
method :post
content-type "application/edn"
encode-fn pr-str
decode-fn read-string}})Create a remote that invokes a Ring handler.
Simulates a full HTTP round-trip through the Ring middleware stack, which is useful for testing authentication, authorization, and other middleware behavior.
ring-handler: A Ring handler function (fn [request] response)
Options:
Example:
(ring-remote my-ring-app
:uri "/api/graphql"
:session {:user/id 1}
:headers {"Authorization" "Bearer token"})
Create a remote that invokes a Ring handler.
Simulates a full HTTP round-trip through the Ring middleware stack,
which is useful for testing authentication, authorization, and other
middleware behavior.
ring-handler: A Ring handler function (fn [request] response)
Options:
- :uri - API endpoint URI (default "/api")
- :method - HTTP method (default :post)
- :content-type - Request content type (default "application/transit+json")
- :headers - Additional headers to include
- :session - Session data to include in request
- :encode-fn - (fn [eql] encoded-body) - defaults to pr-str
- :decode-fn - (fn [response-body] data) - defaults to read-string
Example:
```clojure
(ring-remote my-ring-app
:uri "/api/graphql"
:session {:user/id 1}
:headers {"Authorization" "Bearer token"})
```(sync-remote handler-fn
&
{:keys [simulate-latency-ms transform-request transform-response
on-error]})Create a synchronous remote that calls handler-fn directly.
handler-fn: (fn [eql-request] response-body) OR returns a core.async channel that will contain the response
The handler receives the EQL query/mutation as data and should return the response body (the data to merge into app state).
Options:
Example:
(sync-remote
(fn [eql]
{:user/id 1 :user/name "John"}))
Create a synchronous remote that calls handler-fn directly.
handler-fn: (fn [eql-request] response-body)
OR returns a core.async channel that will contain the response
The handler receives the EQL query/mutation as data and should return
the response body (the data to merge into app state).
Options:
- :simulate-latency-ms - Add artificial delay (for testing loading states)
- :transform-request - (fn [eql] transformed-eql) before calling handler
- :transform-response - (fn [response] transformed-response) after handler
- :on-error - (fn [error eql] {:status-code :body}) custom error handling
Example:
```clojure
(sync-remote
(fn [eql]
{:user/id 1 :user/name "John"}))
```(wrap-fulcro-request)(wrap-fulcro-request handler)(wrap-fulcro-request handler transit-handlers)CLJ port of the CLJS wrap-fulcro-request middleware. Encodes the request body as transit+json and sets appropriate headers.
Options:
CLJ port of the CLJS wrap-fulcro-request middleware. Encodes the request body as transit+json and sets appropriate headers. Options: - :transit-handlers - Additional transit write handlers (map of type to handler)
(wrap-fulcro-response)(wrap-fulcro-response handler)(wrap-fulcro-response handler transit-handlers)CLJ port of the CLJS wrap-fulcro-response middleware. Decodes transit+json response body back to EDN.
Options:
CLJ port of the CLJS wrap-fulcro-response middleware. Decodes transit+json response body back to EDN. Options: - :transit-handlers - Additional transit read handlers (map of tag to handler)
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |