Nodos
Paragraph
Prosa, renderizada a partir de tus datos o resuelta desde prompts en el momento de la petición.
The workhorse. A static paragraph returns a string from your typed data; a dynamic one carries prompts and a placeholder, and is filled at request time. Both land as the same node kind, differing only by `mode`.
- Helpers
- paragraph
- Clase de nodo
- paragraph
- Categoría
- Texto
- Se resuelve
- Both
- Hijos
- None. Paragraphs are leaves.
| Opción | Tipo | Qué hace |
|---|---|---|
id obligatorio | string | Stable address for the node. Generation endpoints target it and build artifacts diff on it, so treat a rename as a breaking change. |
render obligatorio | (data, availableTokens) => string | Static only. Receives your typed data and the token budget, returns the text. May be async. |
placeholder | (data, availableTokens) => string | Dynamic only. What previews show in place of generated content. Optional, but a letter that reads badly without one cannot be reviewed. |
generalPrompt obligatorio | (data, availableTokens) => string | Dynamic only. What this node should say. |
infoPrompt | (data, availableTokens) => string | Dynamic only. Context the model should have but should not restate. |
negativePrompt | (data, availableTokens) => string | Dynamic only. What to avoid — claims, tones, or facts it must not invent. |
systemPrompt | (data, availableTokens) => string | Dynamic only. Role and voice, applied ahead of the other prompts. |
derivers | DeriverInvocation[] | Values computed before the node resolves, written to derived.* and readable from a template token. Built with derive(). |
Escribir uno
render recibe tus datos tipados y el presupuesto de tokens, y devuelve una
cadena. No hay ningún lenguaje de plantillas de por medio, así que el formato,
las bifurcaciones y los plurales son TypeScript corriente:
import { paragraph } from "docxcelerate";
import type { MemberData } from "../types.ts";
export const Greeting = paragraph<MemberData>({
id: "greeting",
render: (data) => `Dear ${data.memberName},`,
});
Puede ser async — pero un nodo que hace peticiones es un nodo que puede fallar
a mitad de la compilación, así que es preferible poner antes el valor en tus
datos.
El presupuesto de tokens
El segundo argumento de render, de placeholder y de cada prompt es
availableTokens: el presupuesto asignado a este nodo, 2000 salvo que definas
availableTokens en las opciones de compilación. Los nodos estáticos pueden
ignorarlo. Los dinámicos deberían gastarlo:
generalPrompt: (data, availableTokens) =>
`Explain the change. At most ${Math.floor(availableTokens / 4)} words.`,
Las bifurcaciones van dentro del render
Un documento que dice una de tres cosas es un nodo con tres desenlaces, no tres nodos detrás de condiciones. El id se queda donde está, el árbol conserva su forma sea quien sea el destinatario, y la lógica es código que puedes probar.
Párrafos dinámicos
Un párrafo dinámico cambia render por prompts y un marcador de posición. Las
compilaciones de vista previa lo resuelven al marcador y lo etiquetan; las
compilaciones en el momento de la petición envían los prompts. Solo
generalPrompt es obligatorio — la cuarta variante de abajo muestra para qué
sirven los otros tres.
Variantes
Static
src/nodes/paragraph/static.node.ts Data in, a line of text out.
import { paragraph } from "docxcelerate";
import type { SampleData } from "../sample-data.ts";
/** The smallest useful node: an id, and a render that turns data into a line. */
export const Greeting = paragraph<SampleData>({
id: "greeting",
render: (data) => `Dear ${data.memberName},`,
}); En qué se resuelve
El nodo tal como aparece en el DocumentModel: el JSON que recibe un renderizador. Sin estilos, sin maquetación.
{
"id": "greeting",
"kind": "paragraph",
"mode": "static",
"text": "Dear Adaeze Nkemelu,"
} Static, with branching
src/nodes/paragraph/conditional.node.ts One node, several outcomes — and the id stays put.
import { paragraph } from "docxcelerate";
import { money, type SampleData } from "../sample-data.ts";
/**
* Branching lives in the render, not the template: one node and one id,
* whichever branch a member falls down.
*/
export const PriceChange = paragraph<SampleData>({
id: "price-change",
render: (data) => {
const delta = data.newPrice - data.lastPrice;
if (delta === 0) {
return `Your ${data.plan} membership renews at ${money(data.newPrice)} ` +
`a year — the same price you paid last year.`;
}
const direction = delta > 0 ? "rising" : "falling";
const percent = Math.abs((delta / data.lastPrice) * 100).toFixed(1);
return `Your ${data.plan} membership is ${direction} by ${percent}%, from ` +
`${money(data.lastPrice)} to ${money(data.newPrice)} a year. That is ` +
`${money(data.newPrice / 12)} a month from ${data.renewsOn}.`;
},
}); En qué se resuelve
El nodo tal como aparece en el DocumentModel: el JSON que recibe un renderizador. Sin estilos, sin maquetación.
{
"id": "price-change",
"kind": "paragraph",
"mode": "static",
"text": "Your Peak Anytime membership is rising by 5.1%, from £468 to £492 a year. That is £41 a month from 1 October 2026."
} Dynamic
src/nodes/paragraph/dynamic.node.ts A prompt and a placeholder. Previews show the placeholder, labelled.
import { paragraph } from "docxcelerate";
import type { SampleData } from "../sample-data.ts";
/**
* The minimum a dynamic paragraph needs: one prompt, and a placeholder so the
* preview still reads as a letter.
*/
export const NextSteps = paragraph<SampleData>({
id: "next-steps",
placeholder: (data) =>
`Your membership renews automatically on ${data.renewsOn}. ` +
`Nothing is needed from you unless you want to change plan.`,
generalPrompt: (data) =>
`In two sentences, tell ${data.memberName} that their membership renews ` +
`automatically on ${data.renewsOn} and how to change plan before then.`,
}); En qué se resuelve
El nodo tal como aparece en el DocumentModel: el JSON que recibe un renderizador. Sin estilos, sin maquetación.
{
"id": "next-steps",
"kind": "paragraph",
"mode": "dynamic",
"text": "Your membership renews automatically on 1 October 2026. Nothing is needed from you unless you want to change plan."
} Qué se le pide al endpoint
Resuelto con los mismos datos de ejemplo. Una compilación de vista previa se detiene en el marcador de posición; una compilación en el momento de la petición envía estos.
- general
- In two sentences, tell Adaeze Nkemelu that their membership renews automatically on 1 October 2026 and how to change plan before then.
Dynamic, all four prompts
src/nodes/paragraph/prompted.node.ts System, general, info and negative, each doing one job.
import { paragraph } from "docxcelerate";
import { money, type SampleData } from "../sample-data.ts";
/**
* All four slots: general says what to write, info supplies facts without
* asking for them back, negative fences off the failure modes, system fixes
* the voice. `availableTokens` is the budget the build allotted this node.
*/
export const Apology = paragraph<SampleData>({
id: "pool-closure",
placeholder: () =>
`The main pool is closed for resurfacing until 12 October. ` +
`The teaching pool and all land-based classes are running as normal.`,
systemPrompt: () =>
`You write for a public leisure centre. Plain British English, second ` +
`person, no marketing language.`,
generalPrompt: (data, availableTokens) =>
`Apologise to ${data.memberName} for the main pool closure and say what ` +
`is still open. At most ${Math.floor(availableTokens / 4)} words.`,
infoPrompt: (data) =>
`The main pool at ${data.centreName} is resurfacing until 12 October. ` +
`The teaching pool, gym and classes are unaffected. Members on ` +
`${data.plan} paying ${money(data.newPrice)} a year get two guest passes ` +
`as compensation.`,
negativePrompt: () =>
`Do not promise a refund, do not give a reopening date beyond 12 October, ` +
`and do not restate the price.`,
}); En qué se resuelve
El nodo tal como aparece en el DocumentModel: el JSON que recibe un renderizador. Sin estilos, sin maquetación.
{
"id": "pool-closure",
"kind": "paragraph",
"mode": "dynamic",
"text": "The main pool is closed for resurfacing until 12 October. The teaching pool and all land-based classes are running as normal."
} Qué se le pide al endpoint
Resuelto con los mismos datos de ejemplo. Una compilación de vista previa se detiene en el marcador de posición; una compilación en el momento de la petición envía estos.
- system
- You write for a public leisure centre. Plain British English, second person, no marketing language.
- general
- Apologise to Adaeze Nkemelu for the main pool closure and say what is still open. At most 500 words.
- info
- The main pool at Riverside Leisure Centre is resurfacing until 12 October. The teaching pool, gym and classes are unaffected. Members on Peak Anytime paying £492 a year get two guest passes as compensation.
- negative
- Do not promise a refund, do not give a reopening date beyond 12 October, and do not restate the price.
Notas
- Ambos modos se resuelven a
kind: "paragraph". Un renderizador leemodesi es que le importa; nunca se bifurca según el helper que hayas llamado. - Una cadena vacía devuelve un párrafo vacío, no la ausencia de nodo. Para eliminar el nodo, déjalo fuera del árbol.
- Los renderizadores escapan el texto. Un párrafo no puede colar marcado en la página, y no es el sitio para dar formato.