Documentation
¶
Overview ¶
Package emaildoc turns an editor document into email-ready HTML, with limits: a document in, HTML or an error out (ADR 0040).
A document is the JSON of @react-email/editor. The render is the editor's own export code (a generated bundle, `mise run build:emaildoc`) run in an embedded JavaScript runtime (goja): no Node, no sidecar, no cgo. A runtime takes seconds to start and tens of megabytes, so it starts on the first call, not at boot; calls queue for it. A call that runs past its time or memory limit is stopped and its runtime is discarded (a stopped runtime does not recover), and a fresh one starts on the next call. Inputs are capped (size, depth, node count) before they reach a runtime.
A document is also validated: structurally against the editor's schema and by rules of ours (heading levels, a button's attributes, a link's href, allowed attribute values). An invalid document is refused with a reason an agent can act on; nothing is repaired or dropped.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrInvalid = errors.New("the document is invalid") ErrTooLarge = errors.New("the document is too large") ErrTooDeep = errors.New("the document is nested too deeply") ErrTooManyNodes = errors.New("the document has too many nodes") ErrTimeout = errors.New("the render took too long and was stopped") ErrMemoryLimit = errors.New("the render used too much memory and was stopped") )
Errors a call can end with, to be told apart with errors.Is. An invalid document is an *InvalidError (errors.Is(err, ErrInvalid)).
Functions ¶
func EditorVersion ¶
func EditorVersion() string
EditorVersion is the version of the editor whose export code the renderer runs. A stored document records it, so a later dependency update can tell which documents it has to re-render.
Types ¶
type AgentAttr ¶
type AgentAttr struct {
Default json.RawMessage
Type string
Allowed []string
Rule string
}
AgentAttr is one attribute of a node or mark as an agent is told it. The editor's schema holds only the default; the type, the allowed values and the rule are ours, written beside the checks in rules.go that enforce them (the same alignments, list types and URL schemes), so a document that follows what an agent is told is not refused.
type AgentDescription ¶
type AgentDescription struct {
EditorVersion string
Nodes map[string]AgentType
Marks map[string]AgentType
Rules []string
Example json.RawMessage
}
AgentDescription is the document format as an agent reads it: the structure the editor derives, the attribute rules, the rules across the document and a valid document to start from.
func AgentSchema ¶
func AgentSchema() AgentDescription
AgentSchema describes the editor's document for an agent.
type InvalidError ¶
type InvalidError struct {
Reason string
}
InvalidError is a document that the editor cannot hold, with the reason in words an author can act on.
func (*InvalidError) Error ¶
func (e *InvalidError) Error() string
func (*InvalidError) Is ¶
func (e *InvalidError) Is(target error) bool
Is makes every *InvalidError match ErrInvalid.
type Limits ¶
type Limits struct {
// MaxBytes is the size of the document's JSON.
MaxBytes int
// MaxDepth is how deep nodes nest (the document is 1).
MaxDepth int
// MaxNodes is how many nodes the document holds in all, text nodes included.
MaxNodes int
// Timeout is how long one call may run once a runtime is ready.
Timeout time.Duration
// StartTimeout is how long a call waits for a runtime to start (the first call of a process,
// or the first after one was discarded), on top of Timeout. A start takes seconds, many more on
// a busy machine; a call that gives up leaves the start running for the next one.
StartTimeout time.Duration
// MemoryGrowth is how many bytes the process heap may grow during one call. The heap counter is
// process-wide, so another goroutine's allocations count too; the call is stopped only when the
// growth stays over the limit for a fifth of a second, which a passing burst does not, so the
// limit is a guard, not an accounting.
MemoryGrowth uint64
}
Limits cap a document before it reaches a runtime.
func DefaultLimits ¶
func DefaultLimits() Limits
DefaultLimits are sized for an email: the prototype rendered 1,000 paragraphs in about 8 seconds and 1,000 nested levels in about 10, so a document stays well under both.
type Option ¶
Option configures New.
func WithRuntimes ¶
WithRuntimes sets how many runtimes the Renderer may run at once (default 1). Each costs about 16 MB and seconds to start, the first about 68 MB.
type Rendered ¶
type Rendered struct {
// HTML is the full email document, formatted.
HTML string
// Text is the plain-text part derived from it.
Text string
}
Rendered is the editor's export of a document.
type Renderer ¶
type Renderer struct {
// contains filtered or unexported fields
}
Renderer renders and validates documents. It is safe for concurrent use. The zero value is not usable: build one with New.
func Default ¶
func Default() *Renderer
Default is the process-wide Renderer with the default limits and one runtime.
func (*Renderer) Render ¶
Render validates doc and exports it as an email: HTML and a plain-text part. It never repairs a document; an invalid one is refused with the reason.
type Schema ¶
type Schema struct {
Nodes map[string]SchemaType `json:"nodes"`
Marks map[string]SchemaType `json:"marks"`
}
Schema is what the editor's extensions define: every node and mark with its content expression and the attributes it accepts, with their defaults. ProseMirror keeps no attribute types or allowed values, so those live in this package's rules and in what an agent is told by hand.
type SchemaType ¶
type SchemaType struct {
// Content is the ProseMirror content expression ("paragraph block*"); empty for a leaf.
Content string `json:"content,omitempty"`
// Group is the group the node belongs to ("block", "inline"); empty for a mark.
Group string `json:"group,omitempty"`
// Attrs maps each accepted attribute to its default.
Attrs map[string]any `json:"attrs"`
}
SchemaType is one node or mark.