All docs

Docs · Reference

read-file

load FILES/ assets into the document

txco://read-file reads a stack's FILES/ assets into the document as DATA — so a rule can template them, hash them, return them, or hand them to another op. It's the read-into-the-tree counterpart to txco://static, which serves a file straight back as an HTTP response.

The headline use is mail — read FILES/_mail/welcome.html and pass it to txco://sendmail — but it’s general: config, fixtures, prerendered fragments, anything you ship under FILES/.

Read a file

WITH files = &array(&object("path", "_mail/welcome.html", "as", "welcome"))
EXEC "txco://read-file"

The bytes are now at ._files.welcome.content. A later op can use them — e.g. reply with the template (sendmail reads the _sendmail contract):

WHEN ._files.welcome.found == true
  SET ._sendmail.to      = @lmtp.mail.from,
      ._sendmail.from    = @lmtp.rcpt.0,
      ._sendmail.subject = "Welcome",
      ._sendmail.body    = ._files.welcome.content
  EXEC "txco://sendmail"

(Read in one scope, send in a later one — same-scope ops run in parallel, so the file must be loaded before the op that consumes it.)

files is a list of { path, as }

WITH files = &array(
  &object("path", "_mail/welcome.html", "as", "welcome"),
  &object("path", "config/limits.json", "as", "limits"))
EXEC "txco://read-file"
  • path — a FILES/-relative path within the routed stack. _-prefixed paths work (that’s how _mail/ templates are read — they’re indexed but never served over HTTP).
  • as — the key the result lands under. Required, unique, no . or /. Results are keyed by your alias, not the file path, so downstream addressing stays clean and decoupled from the on-disk layout.

The result shape

Each file lands under into (default _files), keyed by as:

{
  "_files": {
    "welcome": {
      "found": true,
      "content": "<!doctype html>…",
      "encoding": "utf8",
      "ctype": "text/html; charset=utf-8",
      "size": 1234,
      "path": "_mail/welcome.html"
    }
  }
}

_files is _-prefixed, so it’s dropped from the default web response — scratch the client never sees. A missing file is { "found": false, "path": … }, not an error (unless strict, below). The result is an object keyed by alias (never an array), so re-running the op overwrites cleanly instead of appending.

Dynamic paths

files is resolved before the op runs, so paths can be computed from the document — request fields, a manifest, the recipient:

WITH files = &array(&object(
  "path", &concat("docs/", @web.req.url.query.name.0, ".md"),
  "as",   "doc"))
EXEC "txco://read-file"

A prior op can also assemble the whole array; then you just pass files = .the_array.

Options

WITHDefaultDoes
files— (required)&array of { path, as }.
into_filesWhere the result object lands.
encodeautoauto = UTF-8 text as a string, binary as base64; force with utf8 / base64.
strictfalseA missing or over-cap file fails the op instead of found:false.
max_bytes--read-file-max-bytes (1 MiB)Per-file cap; over-cap is truncated (or errors under strict).

Each entry’s encoding tells a consumer how to read content, so binary assets round-trip as base64 without corrupting the document.

Scope & safety

  • Reads only the routed tenant + stack’s FILES/ (then the workspace-wide and embedded layers) — never another tenant’s, never an arbitrary host path.
  • Path-cleaned: .. traversal is rejected.
  • Pure in-memory — it reads the same content-addressed index txco://static uses, so it never touches the filesystem on the request path.
  • Pays normal fuel and shows up in traces.

Edit this page · View as markdown