safsaf/CLAUDE.md
Christopher Baines 5b0e6397dc
All checks were successful
/ test (push) Successful in 9s
Initial commit
Safsaf is a Guile web framework, written using Claude Code running
Claude Opus 4.6, based off of the Guix Data Service, Nar Herder and
Guix Build Coordinator codebases.
2026-04-13 14:24:19 +03:00

6.4 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Safsaf is a web framework for Guile Scheme, built on Guile Fibers using the Guile Knots web server.

Environment Setup

The project uses direnv with Guix. The .envrc runs use guix -D -f guix-dev.scm, which pulls in all dependencies: guile-knots, guile-webutils, guile-lib, guile-json-4, guile-squee, guile-sqlite3, guile-gcrypt.

Run direnv allow to activate the environment. All Guile dependencies are on GUILE_LOAD_PATH via the Guix profile.

Key Dependencies

Guile Knots

  • (knots web-server)run-knots-web-server: the HTTP server. Handler signature is (request body-port) → (values response body). body-port is the port for reading the request body lazily.
  • (knots resource-pool)with-resource-from-pool: DB/resource connection pooling.
  • (knots parallelism)fibers-let, fibers-parallel, fibers-map: concurrent work in handlers.
  • (knots thread-pool)call-with-thread: offload blocking/CPU-bound work.
  • (knots timeout)with-fibers-timeout, with-port-timeouts: request and I/O timeouts.
  • (knots web)call-with-connection-cache: outbound HTTP with connection pooling.
  • (knots)call-with-sigint, format/knots, spawn-fiber/knots.
  • (knots web-server) also exports make-chunked-output-port/knots, sanitize-response, request-body-port/knots, read-request-body/knots.

Guile Webutils

  • (webutils multipart)parse-request-body, <part> record, parts-ref, parts-ref-string.
  • (webutils cookie)set-cookie, delete-cookie. Registers Cookie/Set-Cookie header parsers with (web http).
  • (webutils sessions)<session-manager>, HMAC-signed cookie sessions. Format: signature$expires$base64-data.
  • (webutils date) — RFC3339 and HTTP date conversions.

Guile JSON (v4.7.3)

  • (json) — Re-exports everything from parser, builder, and record modules.
  • (json parser)json->scm (from port), json-string->scm (from string). Options: #:null (default 'null), #:ordered (preserve key order).
  • (json builder)scm->json (to port), scm->json-string (to string). Options: #:pretty, #:unicode, #:validate.
  • (json record)define-json-mapping: bidirectional SRFI-9 record ↔ JSON conversion.
  • Data mapping: objects ↔ alists, arrays ↔ vectors, strings ↔ strings, numbers ↔ numbers, true/false#t/#f, null'null.

Guile Gcrypt

  • (gcrypt random) — cryptographic random bytes (used for CSRF token generation).

Guile Lib

  • (htmlprag) — HTML/SHTML parsing and generation. html->shtml: parse HTML to SXML. shtml->html: render SXML to HTML string. write-shtml-as-html: write SXML to port.
  • (logging logger) — Logging framework. (logging port-log) — log to ports. (logging rotating-log) — rotating file logs.
  • (md5) — MD5 hashing.
  • (container async-queue)make-async-queue, async-enqueue!, async-dequeue!.
  • (string transform), (string wrap), (string completion) — String utilities.

Guile Standard Library

  • (web request), (web response), (web uri), (web http) — Guile's built-in HTTP types.
  • (srfi srfi-9) — Record types. (srfi srfi-64) — Test framework. (srfi srfi-71) — Extended let with multiple values; prefer over (srfi srfi-11) let-values.

Architecture

Handler signature throughout is (request body-port) → (values response body), using Guile's <request> directly. body-port is the port for reading the request body lazily. Context is threaded via Guile parameters, not a wrapper record.

Safsaf wraps run-knots-web-server with:

  1. Parameters for contextcurrent-route-params (alist of matched route bindings), current-reverse-routes (for path-for). Handler wrappers add their own parameters (e.g. current-csrf-token, current-session).
  2. Router — data-driven route table using (route method pattern handler). Patterns are lists of segments: strings (literal match), symbols (capture), or (predicate name) pairs. Dotted-tail patterns (e.g. '("api" . rest)) capture remaining segments. Routes can be organized with (route-group prefix ...). Named routes support reverse routing via path-for.
  3. Handler wrappers — convention: (foo-handler-wrapper handler) → handler'. A handler wrapper transforms the request on the way in and the response/body on the way out. Wrappers that need configuration provide a (make-foo-handler-wrapper ...) constructor. Applied to route trees via wrap-routes, which accepts one or more wrappers.
  4. Entry point(run-safsaf routes #:key host port method-not-allowed? method-not-allowed-handler connection-buffer-size) compiles the route table, builds the dispatch handler, and starts the HTTP server via run-knots-web-server. When called outside a Fibers scheduler, it wraps everything in run-fibers and blocks until Ctrl-C. When called inside an existing scheduler (e.g. within run-fibers), it just starts the server and returns immediately. method-not-allowed? defaults to #t, enabling automatic 405 responses. Handler wrappers are applied to routes via wrap-routes before passing to run-safsaf.

Finding Guile Library Sources

To read source code for Guile dependencies, look them up via GUILE_LOAD_PATH. The first entry is the project's Guix profile directory containing all dependencies. Do not search /gnu/store directly — it is slow and noisy.

A Guile module path like (knots web-server) maps to the file knots/web-server.scm under a load path directory. To find it:

ls "$GUILE_LOAD_PATH" | head   # see what's available
cat "$(echo $GUILE_LOAD_PATH | cut -d: -f1)/knots/web-server.scm"  # read a specific module

Or use the Read/Glob tools directly against the first GUILE_LOAD_PATH entry (e.g. /gnu/store/...-profile/share/guile/site/3.0/). Module path segments map to directories, with the final segment as <name>.scm. For example:

  • (json parser)json/parser.scm
  • (webutils multipart)webutils/multipart.scm
  • (srfi srfi-9)srfi/srfi-9.scm

Guile Conventions

  • Predicates end with ?. Setters use set- prefix. Constructors use make-.
  • Records defined with define-record-type from (srfi srfi-9).
  • Modules use define-module with #:use-module and #:export.
  • Use values for multiple return values, call-with-values or receive to consume them.