diff --git a/AGENTS.md b/AGENTS.md index 0df3121..f2e7d06 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,10 +31,10 @@ ESLint and Prettier both run in CI. Tests are Vitest, not Jest. `markup-generato ``` src/index.ts public exports only — consumers import the package root -src/runtime/types.ts template, context, trace, and job types +src/runtime/types.ts template, context, and trace types src/runtime/render.ts public re-exports src/runtime/renderTemplate.ts renderTemplate from @llazyemail/render-template -src/runtime/api.ts defineTemplate, renderMany, slot, hasSlot, RenderError +src/runtime/api.ts defineTemplate, slot, hasSlot src/runtime/errors.ts DisplayError, codes, and message catalog src/runtime/explain.ts explain() src/templates/email/email.ts defineEmailTemplate, renderEmail @@ -76,7 +76,7 @@ Each part receives one context: `templateId`, the same props object the caller p `explain` prints a short status report. A thrown `RenderError` prints its message. A result prints `ok` or `failed`, the part count, and the HTML length. Each part is one line: id, status, chars, ms, and the error when there is one. Full HTML stays on `parts[].html`. -`renderMany` runs jobs that share one props type. Throw mode stops the batch at the first broken job, and later jobs do not run. Pass `onError: 'collect'` when one broken template must not hide the others. Jobs may omit `props` (`{}`) and `slots`. +`renderMany`, `RenderJob`, `RenderOptions`, and `RenderError` come from `@llazyemail/render-template`. This package re-exports them. `renderMany` runs jobs that share one props type. Throw mode stops the batch at the first broken job, and later jobs do not run. Pass `onError: 'collect'` when one broken template must not hide the others. Jobs may omit `props` (`{}`) and `slots`. Custom code is a part, or a slot that a part reads with `slot(ctx, id)`. A missing slot is `''`. `hasSlot` is true for an empty string. A function slot is called with the same context. `null` or `undefined` from that function is `''`. A non-string slot, or a non-string return, throws `DisplayError` (`slot_type` or `slot_return`) and the part that called `slot` is the part that fails. Package-owned messages are built in `src/runtime/errors.ts`. Render and validation failures stay `RenderError`. diff --git a/README.md b/README.md index 5aa38d2..eb59165 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ const result = renderEmail(template, props, { onError: 'collect' }); explain(result); ``` -`collect` records the broken part, uses `''` for it, and continues. `result.ok` is false. Full HTML for every part that ran stays on `result.parts[].html`. `renderMany` uses the same switch. In throw mode the first broken job stops the batch. Pass `collect` when one failure must not hide the rest. Jobs in one batch share a props type. +`collect` records the broken part, uses `''` for it, and continues. `result.ok` is false. Full HTML for every part that ran stays on `result.parts[].html`. `renderMany` is `@llazyemail/render-template`'s batch helper, re-exported here, and uses the same switch. In throw mode the first broken job stops the batch. Pass `collect` when one failure must not hide the rest. Jobs in one batch share a props type. ## A document that is not the email shell diff --git a/llms-full.txt b/llms-full.txt index b0c0c57..b406a9d 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -71,6 +71,8 @@ A function slot is called with the same context. `null` and `undefined` become ` ## renderMany(jobs, options?): RenderResult[] +Re-exported from `@llazyemail/render-template`, along with `RenderJob`, `RenderOptions`, and `RenderError`. This package does not define them. + Every job shares `TProps`. A job is `{ template, props?, slots? }`. Omitted props become `{}`. `options` is only `{ onError? }`. `throw` (default) lets the first `RenderError` escape. Later jobs do not run. `collect` returns one result per job, including failures. diff --git a/llms.txt b/llms.txt index 0ac2c8c..9a00f6f 100644 --- a/llms.txt +++ b/llms.txt @@ -36,6 +36,6 @@ explain(result); result.html; ``` -`renderTemplate` and `defineTemplate` are the same runtime without the email shell. `renderMany` batches jobs that share one props type. Default `onError` is `throw` (`RenderError` with `partId`, `trace`, and `cause`). `collect` keeps going and sets `ok: false`. +`renderTemplate` and `defineTemplate` are the same runtime without the email shell. `renderMany`, `RenderJob`, `RenderOptions`, and `RenderError` are re-exported from `@llazyemail/render-template`. `renderMany` batches jobs that share one props type. Default `onError` is `throw` (`RenderError` with `partId`, `trace`, and `cause`). `collect` keeps going and sets `ok: false`. Fragment helpers `head`, `main`, `footer`, `body`, `content`, and `document` return strings. Omitted strings are empty. The button needs both `ctaLabel` and `ctaUrl`. Unsubscribe needs `unsubscribeUrl`. Props and slots are raw HTML. `document` uses ``. diff --git a/package-lock.json b/package-lock.json index 39d3428..71227ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -813,8 +813,8 @@ "license": "MIT" }, "node_modules/@llazyemail/render-template": { - "version": "0.1.2", - "resolved": "git+ssh://git@github.com/LLazyEmail/render-template.git#486aac4e4744f5868c9a98148a38faa5d5456e4d", + "version": "0.3.0", + "resolved": "git+ssh://git@github.com/LLazyEmail/render-template.git#ff0702d378646ad921da2b1870425e01df16e7dc", "license": "MIT", "engines": { "node": ">=22" diff --git a/src/index.ts b/src/index.ts index 7d203fe..9103242 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,25 +2,23 @@ export { defineTemplate, displayErrors, hasSlot, - renderMany, renderTemplate, slot, DisplayError, DisplayErrorCode, - RenderError, } from './runtime/render'; +export { renderMany, RenderError } from '@llazyemail/render-template'; export type { PartFailure, PartStatus, PartTrace, RenderContext, - RenderJob, - RenderOptions, RenderResult, SlotValue, Template, TemplatePart, } from './runtime/types'; +export type { RenderJob, RenderOptions } from '@llazyemail/render-template'; export { explain } from './runtime/explain'; export { defineEmailTemplate, renderEmail } from './templates/email/email'; diff --git a/src/runtime/api.ts b/src/runtime/api.ts index 2432b64..897f2c4 100644 --- a/src/runtime/api.ts +++ b/src/runtime/api.ts @@ -1,9 +1,7 @@ import { assertTemplate, type Template as PackageTemplate } from '@llazyemail/render-template'; -import type { RenderContext, RenderJob, RenderOptions, RenderResult, Template } from './types'; +import type { RenderContext, Template } from './types'; import { displayErrors } from './errors'; -import { renderTemplate } from './renderTemplate'; -export { RenderError } from '@llazyemail/render-template'; export { DisplayError, DisplayErrorCode, displayErrors } from './errors'; /** @@ -21,20 +19,6 @@ export function defineTemplate(template: Template): Template( - jobs: readonly RenderJob[], - options?: Pick, 'onError'>, -): RenderResult[] { - return jobs.map((job) => { - const next: RenderOptions = {}; - if (options?.onError !== undefined) next.onError = options.onError; - if (job.slots !== undefined) next.slots = job.slots; - const props = job.props === undefined ? ({} as TProps) : job.props; - return renderTemplate(job.template, props, next); - }); -} - /** Read a slot. A missing slot is `''`. A function slot is called with the same context. */ export function slot(ctx: RenderContext, id: string): string { if (!hasSlot(ctx, id)) return ''; diff --git a/src/runtime/explain.ts b/src/runtime/explain.ts index 1b3197a..5ded7b6 100644 --- a/src/runtime/explain.ts +++ b/src/runtime/explain.ts @@ -1,4 +1,4 @@ -import { RenderError } from './render'; +import { RenderError } from '@llazyemail/render-template'; import type { RenderResult } from './types'; /** One text block for logs. Full HTML stays on each part trace. */ diff --git a/src/runtime/render.ts b/src/runtime/render.ts index a9076fa..ef215e4 100644 --- a/src/runtime/render.ts +++ b/src/runtime/render.ts @@ -2,9 +2,7 @@ export { renderTemplate } from './renderTemplate'; export { defineTemplate, hasSlot, - renderMany, slot, - RenderError, DisplayError, DisplayErrorCode, displayErrors, diff --git a/src/runtime/renderTemplate.ts b/src/runtime/renderTemplate.ts index 41469c0..2e0b7b4 100644 --- a/src/runtime/renderTemplate.ts +++ b/src/runtime/renderTemplate.ts @@ -1,10 +1,10 @@ import { renderTemplate as renderPackageTemplate, - type RenderOptions as PackageRenderOptions, + type RenderOptions, type RenderResult as PackageRenderResult, type Template as PackageTemplate, } from '@llazyemail/render-template'; -import type { RenderOptions, RenderResult, Template } from './types'; +import type { RenderResult, Template } from './types'; /** * Render one template. The same template and props produce the same HTML. @@ -18,6 +18,6 @@ export function renderTemplate( return renderPackageTemplate( template as PackageTemplate, props, - options as PackageRenderOptions, + options, ) as PackageRenderResult as RenderResult; } diff --git a/src/runtime/types.ts b/src/runtime/types.ts index 6246608..189acd6 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -1,5 +1,6 @@ /** HTML string, or a function that receives the same context as a part. */ -export type SlotValue = string | ((ctx: RenderContext) => string); +export type SlotValue = + string | ((ctx: RenderContext) => string | null | undefined); /** One named step. The id is the name that shows up when this step breaks. */ export interface TemplatePart { @@ -50,20 +51,3 @@ export interface RenderResult { /** Set when `compose` itself throws and `onError` is `collect`. */ composeError?: PartFailure; } - -export interface RenderOptions { - /** - * `throw` stops at the first broken part and throws `RenderError`. - * `collect` records the error, uses `''` for that part, and continues. - * Later templates in `renderMany` still run. - */ - onError?: 'throw' | 'collect'; - slots?: Readonly>>; -} - -/** One template in a batch. Props are whatever that template declared. */ -export interface RenderJob { - template: Template; - props?: TProps; - slots?: Readonly>>; -} diff --git a/src/templates/email/email.ts b/src/templates/email/email.ts index db44a4a..b563f4d 100644 --- a/src/templates/email/email.ts +++ b/src/templates/email/email.ts @@ -1,8 +1,9 @@ +import type { RenderOptions } from '@llazyemail/render-template'; import { footer } from '../footer/footer'; import { head } from '../head/head'; import { main } from '../main/main'; import { defineTemplate, renderTemplate } from '../../runtime/render'; -import type { RenderOptions, RenderResult, Template } from '../../runtime/types'; +import type { RenderResult, Template } from '../../runtime/types'; import { pickDefined } from '../../utils/pickDefined'; import { composeEmail, shellPart } from './shell'; import type { EmailProps, EmailTemplate } from './types';