duckweb_asset

scalar · LOAD duckweb

duckweb_asset(VARCHAR) -> VARCHAR
duckweb_asset(VARCHAR, VARCHAR) -> VARCHAR

Hands a file to the transport, at whatever URL your route matched.

SELECT duckweb_asset(r.version || '/' || r.platform || '/' || r.name)
FROM repo_request(route) r

The server’s asset directory is otherwise reachable only under the reserved /_duckweb/ prefix. duckweb_asset lifts that restriction: the app decides the URL, and the transport still serves the file.

It returns a whole response, so it must be the entire output for that route — no head before it, no duckweb_end after it. A frame emitted behind a head that has already gone out cannot be answered with a status any more; the server truncates the response and logs rather than letting the frame reach the client.

Why not just read the file in SQL

Because the bytes never enter DuckDB. The transport keeps Content-Length, Content-Type by extension, ETag and 304, and — load-bearing for an extension repository — Range, since DuckDB reads remote files by range. A BLOB cast to VARCHAR would arrive escaped as \xFF text, four bytes for one.

Paths

path is relative to the asset directory. Anything that would leave it — .. segments, an absolute path, percent-encoded variants of either — is a 404, so it is safe to pass user input:

duckweb_asset(duckweb_query(route, 'p'), duckweb_header('X-Asset', '1'))

The optional second argument is extra header lines, built as for duckweb_head.

In the browser tiers there is no filesystem and no transport to hand the frame to, so this always answers 404. It is registered there all the same: a name missing from one tier would stop the whole render macro binding, not just the route that used it.