Spark DSL extension for rendering Typst templates as Ash generic actions.
This extension adds a typst DSL section to your Ash resource where you can
declare reusable templates and render actions. Each render action is transformed
into an Ash generic action that compiles and exports a Typst document, returning
an AshTypst.Document struct.
Usage
Add the extension to your resource:
defmodule MyApp.Invoice do
use Ash.Resource,
domain: MyApp.Domain,
extensions: [AshTypst.Resource]
typst do
root "priv/typst"
template :invoice do
source "invoice.typ"
inputs %{"company" => "Acme Corp"}
end
template :receipt do
# ~TYPST sigil is auto-imported in template blocks
markup ~TYPST"""
#import "data.typ": record, args
= Receipt #args.receipt_number
*Customer:* #record.name
"""
end
render :generate_pdf do
template :invoice
format :pdf
argument :invoice_id, :string, allow_nil?: false
read :one do
filter expr(id == ^arg(:invoice_id))
load [:line_items, :customer]
end
pdf_options do
pdf_standards [:pdf_a_2b]
end
end
end
endThen call the action like any other Ash generic action:
input = Ash.ActionInput.for_action(MyApp.Invoice, :generate_pdf, %{invoice_id: "123"})
{:ok, %AshTypst.Document{format: :pdf, data: pdf_binary}} = Ash.run_action(input)How It Works
Templates are declared in the
typstsection. Each template has either an inlinemarkupstring (the~TYPSTsigil is auto-imported insidetemplateblocks) or asourcefile path relative to therootdirectory.Render actions reference a template and specify an output
format(:pdf,:svg, or:html). They can optionally declare arguments, areadto fetch resource data, and format-specific options likepdf_options.At compile time, the
BuildActionstransformer converts each render entity into a standardAsh.Resource.Actions.Action.At runtime, the action implementation creates a context, sets the template, injects data (arguments and/or read results) into a virtual file, compiles, and exports in the requested format.
Data Injection
The render action injects data into a virtual file (default "data.typ") that your
template can #import:
- No read: only
args(a dictionary of action arguments) is available. - Read
:one: bothrecord(the single resource) andargsare available. - Read
:many:records(an array, streamed in batches) andargsare available.
DSL Reference
For the complete DSL reference with all options, see AshTypst.Resource.
typst
Configuration for Typst template rendering.
Nested DSLs
Options
| Name | Type | Default | Docs |
|---|---|---|---|
root | String.t | {atom, String.t} | "priv/typst" | Root directory for template file resolution. Accepts either: a String.t() — used verbatim. Relative paths resolve against the current working directory and only work when cwd matches the project root (dev/test). a {otp_app, sub_path} tuple — resolved at runtime via Application.app_dir/2, which works in dev, test, and Mix releases (where priv/ lives at <release>/lib/<app>-<version>/priv/...). For releases-friendly setups, prefer the tuple form: root({:my_app, "priv/typst"}) |
font_paths | list(String.t | {atom, String.t}) | [] | Additional font search directories. Each entry may be a string (used verbatim) or a {otp_app, sub_path} tuple (resolved via Application.app_dir/2 at runtime). Mix releases relocate priv/ files, so the tuple form is recommended for paths rooted in your app's priv/. |
ignore_system_fonts | boolean | false | Skip system font loading. |
typst.template
template nameDeclares a reusable Typst template.
Arguments
| Name | Type | Default | Docs |
|---|---|---|---|
name | atom | Unique template identifier. |
Options
| Name | Type | Default | Docs |
|---|---|---|---|
source | String.t | File path relative to the root directory. | |
markup | String.t | Inline Typst markup string (~TYPST sigil is auto-imported). | |
inputs | map | Static sys.inputs key/value pairs (string keys and values). |
Introspection
Target: AshTypst.Resource.Template
typst.render
render nameDeclares a Typst template rendering action.
Nested DSLs
Arguments
| Name | Type | Default | Docs |
|---|---|---|---|
name | atom | Action name (becomes the generic action name). |
Options
| Name | Type | Default | Docs |
|---|---|---|---|
template | atom | Reference to a template declared in the typst section. | |
format | :pdf | :svg | :html | Output export format. | |
description | String.t | Action description. | |
page | non_neg_integer | Page index for SVG rendering. | |
data_file | String.t | "data.typ" | Virtual file path for serialized data. |
transaction? | boolean | false | Wrap action execution in a transaction. |
typst.render.argument
argument name, typeDeclares an argument on the action.
Arguments
| Name | Type | Default | Docs |
|---|---|---|---|
name | atom | The name of the argument | |
type | module | The type of the argument. See Ash.Type for more. |
Options
| Name | Type | Default | Docs |
|---|---|---|---|
description | String.t | An optional description for the argument. | |
constraints | keyword | [] | Constraints to provide to the type when casting the value. For more information, see Ash.Type. |
allow_nil? | boolean | true | Whether or not the argument value may be nil (or may be not provided). If nil value is given error is raised. |
public? | boolean | true | Whether or not the argument should appear in public interfaces |
sensitive? | boolean | false | Whether or not the argument value contains sensitive information, like PII(Personally Identifiable Information). See the security guide for more. |
default | any | The default value for the argument to take. It can be a zero argument function e.g &MyMod.my_fun/0 or a value |
Introspection
Target: Ash.Resource.Actions.Argument
typst.render.read
read cardinalityDeclares how to fetch resource data to pass to the template.
Arguments
| Name | Type | Default | Docs |
|---|---|---|---|
cardinality | :one | :many | :one uses Ash.read_one, :many uses Ash.read. |
Options
| Name | Type | Default | Docs |
|---|---|---|---|
filter | any | Ash filter expression; supports ^arg(:name) to reference action arguments. | |
load | list(any) | [] | Relationships, calculations, and aggregates to load. |
select | list(atom) | Attributes to select (nil = all). | |
sort | any | Sort specification. | |
limit | pos_integer | Max records to return (:many only). | |
batch_size | pos_integer | 100 | Batch size for streaming large datasets into virtual file (:many only). |
not_found | :error | nil | :error | Behavior when :one finds no record. |
Introspection
Target: AshTypst.Resource.Render.Read
typst.render.pdf_options
PDF-specific export options.
Options
| Name | Type | Default | Docs |
|---|---|---|---|
pages | String.t | Page range, 1-indexed (e.g., "1-3,5,7-9"). | |
pdf_standards | list(:pdf_1_7 | :pdf_a_2b | :pdf_a_3b) | [] | PDF compliance standards. |
document_id | String.t | PDF document identifier. |
Introspection
Target: AshTypst.Resource.Render.PdfOptions
typst.render.prepare
prepare preparationDeclares a preparation that runs before the template is rendered.
Arguments
| Name | Type | Default | Docs |
|---|---|---|---|
preparation | (any, any -> any) | module | The module and options for a preparation. Also accepts functions take the query and the context. |
Options
| Name | Type | Default | Docs |
|---|---|---|---|
on | :read | :action | :create | :update | :destroy | list(:read | :action | :create | :update | :destroy) | [:read] | The action types the preparation should run on. By default, preparations only run on read actions. Use :action to run on generic actions. |
where | (any, any -> any) | module | list((any, any -> any) | module) | [] | Validations that should pass in order for this preparation to apply. Any of these validations failing will result in this preparation being ignored. |
only_when_valid? | boolean | false | If the preparation should only run on valid queries. |
Introspection
Target: Ash.Resource.Preparation
typst.render.validate
validate validationDeclares a validation for this action.
Arguments
| Name | Type | Default | Docs |
|---|---|---|---|
validation | (any, any -> any) | module | The module (or module and opts) that implements the Ash.Resource.Validation behaviour. Also accepts a function that receives the changeset and its context. |
Options
| Name | Type | Default | Docs |
|---|---|---|---|
where | (any, any -> any) | module | list((any, any -> any) | module) | [] | Validations that should pass in order for this validation to apply. Any of these validations failing will result in this validation being ignored. |
only_when_valid? | boolean | false | If the validation should only run on valid changesets. Useful for expensive validations or validations that depend on valid data. |
message | String.t | If provided, overrides any message set by the validation error | |
description | String.t | An optional description for the validation | |
before_action? | boolean | false | If set to true, the validation will be run in a before_action hook |
always_atomic? | boolean | false | By default, validations are only run atomically if all changes will be run atomically or if there is no validate/3 callback defined. Set this to true to run it atomically always. |
Introspection
Target: Ash.Resource.Validation
Introspection
Target: AshTypst.Resource.Render