@formfeed/engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -0
  3. package/index.cjs +3306 -0
  4. package/index.js +3273 -0
  5. package/package.json +70 -0
  6. package/types/index.d.cts +17 -0
  7. package/types/index.d.ts +17 -0
  8. package/types/lib/analysis/tags.d.cts +36 -0
  9. package/types/lib/analysis/tags.d.ts +36 -0
  10. package/types/lib/assemble.d.cts +147 -0
  11. package/types/lib/assemble.d.ts +147 -0
  12. package/types/lib/engines/handlebars.d.cts +3 -0
  13. package/types/lib/engines/handlebars.d.ts +3 -0
  14. package/types/lib/engines/index.d.cts +7 -0
  15. package/types/lib/engines/index.d.ts +7 -0
  16. package/types/lib/engines/jinja2.d.cts +5 -0
  17. package/types/lib/engines/jinja2.d.ts +5 -0
  18. package/types/lib/engines/liquid.d.cts +16 -0
  19. package/types/lib/engines/liquid.d.ts +16 -0
  20. package/types/lib/errors.d.cts +24 -0
  21. package/types/lib/errors.d.ts +24 -0
  22. package/types/lib/helpers/codes.d.cts +21 -0
  23. package/types/lib/helpers/codes.d.ts +21 -0
  24. package/types/lib/helpers/collections.d.cts +2 -0
  25. package/types/lib/helpers/collections.d.ts +2 -0
  26. package/types/lib/helpers/document.d.cts +29 -0
  27. package/types/lib/helpers/document.d.ts +29 -0
  28. package/types/lib/helpers/format.d.cts +5 -0
  29. package/types/lib/helpers/format.d.ts +5 -0
  30. package/types/lib/helpers/index.d.cts +17 -0
  31. package/types/lib/helpers/index.d.ts +17 -0
  32. package/types/lib/helpers/logic.d.cts +2 -0
  33. package/types/lib/helpers/logic.d.ts +2 -0
  34. package/types/lib/helpers/registry.d.cts +18 -0
  35. package/types/lib/helpers/registry.d.ts +18 -0
  36. package/types/lib/import/apitemplate.d.cts +45 -0
  37. package/types/lib/import/apitemplate.d.ts +45 -0
  38. package/types/lib/limits.d.cts +10 -0
  39. package/types/lib/limits.d.ts +10 -0
  40. package/types/lib/preview.d.cts +30 -0
  41. package/types/lib/preview.d.ts +30 -0
  42. package/types/lib/schema.d.cts +22 -0
  43. package/types/lib/schema.d.ts +22 -0
  44. package/types/lib/snippets.d.cts +22 -0
  45. package/types/lib/snippets.d.ts +22 -0
  46. package/types/lib/types.d.cts +140 -0
  47. package/types/lib/types.d.ts +140 -0
  48. package/types/types/modules.d.cts +85 -0
  49. package/types/types/modules.d.ts +85 -0
@@ -0,0 +1,29 @@
1
+ import type { HelperDefinition } from '../types.cjs';
2
+ /**
3
+ * Document helpers that emit markup the assembler completes (spec 05 §2): `chart` places a canvas
4
+ * that Chart.js draws inside Chromium (browser preview and render-worker alike, so both paths
5
+ * produce the same pixels), `image` places a sized, cropped picture.
6
+ */
7
+ export interface ChartSpec {
8
+ type?: string;
9
+ data?: unknown;
10
+ options?: Record<string, unknown>;
11
+ width?: number | string;
12
+ height?: number | string;
13
+ }
14
+ export interface ImageOptions {
15
+ width?: number | string;
16
+ height?: number | string;
17
+ fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
18
+ alt?: string;
19
+ class?: string;
20
+ }
21
+ /** The chart payload the assembler's init script reads from `data-ff-chart`. */
22
+ export declare function chartPayload(spec: ChartSpec): {
23
+ type: string;
24
+ data: unknown;
25
+ options: Record<string, unknown>;
26
+ };
27
+ export declare function chartMarkup(spec: ChartSpec, id?: string): string;
28
+ export declare function imageMarkup(url: string, options?: ImageOptions): string;
29
+ export declare const documentHelpers: HelperDefinition[];
@@ -0,0 +1,29 @@
1
+ import type { HelperDefinition } from '../types.js';
2
+ /**
3
+ * Document helpers that emit markup the assembler completes (spec 05 §2): `chart` places a canvas
4
+ * that Chart.js draws inside Chromium (browser preview and render-worker alike, so both paths
5
+ * produce the same pixels), `image` places a sized, cropped picture.
6
+ */
7
+ export interface ChartSpec {
8
+ type?: string;
9
+ data?: unknown;
10
+ options?: Record<string, unknown>;
11
+ width?: number | string;
12
+ height?: number | string;
13
+ }
14
+ export interface ImageOptions {
15
+ width?: number | string;
16
+ height?: number | string;
17
+ fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
18
+ alt?: string;
19
+ class?: string;
20
+ }
21
+ /** The chart payload the assembler's init script reads from `data-ff-chart`. */
22
+ export declare function chartPayload(spec: ChartSpec): {
23
+ type: string;
24
+ data: unknown;
25
+ options: Record<string, unknown>;
26
+ };
27
+ export declare function chartMarkup(spec: ChartSpec, id?: string): string;
28
+ export declare function imageMarkup(url: string, options?: ImageOptions): string;
29
+ export declare const documentHelpers: HelperDefinition[];
@@ -0,0 +1,5 @@
1
+ import type { HelperDefinition } from '../types.cjs';
2
+ export declare function toNumber(value: unknown): number;
3
+ export declare function toDate(value: unknown, tz: string): Date | null;
4
+ export declare function escapeHtml(value: string): string;
5
+ export declare const formatHelpers: HelperDefinition[];
@@ -0,0 +1,5 @@
1
+ import type { HelperDefinition } from '../types.js';
2
+ export declare function toNumber(value: unknown): number;
3
+ export declare function toDate(value: unknown, tz: string): Date | null;
4
+ export declare function escapeHtml(value: string): string;
5
+ export declare const formatHelpers: HelperDefinition[];
@@ -0,0 +1,17 @@
1
+ import type { HelperDefinition, HelperDoc } from '../types.cjs';
2
+ import { DefaultHelperRegistry } from './registry.cjs';
3
+ export { DefaultHelperRegistry } from './registry.cjs';
4
+ export { escapeHtml, toDate, toNumber } from './format.cjs';
5
+ export { epcPayload, qrSvg } from './codes.cjs';
6
+ export { chartMarkup, chartPayload, imageMarkup } from './document.cjs';
7
+ export type { ChartSpec, ImageOptions } from './document.cjs';
8
+ /** Helpers shipped with the product (spec 05 §2), identical in all three engines. */
9
+ export declare const builtinHelpers: HelperDefinition[];
10
+ export declare function createHelperRegistry(extra?: HelperDefinition[]): DefaultHelperRegistry;
11
+ /** Lazily built default registry, shared by analyze() and the engines when none is supplied. */
12
+ export declare function defaultHelpers(): DefaultHelperRegistry;
13
+ /** Documentation entries that feed editor hover and completion (spec 05 §2). */
14
+ export declare function helperDocs(registry?: DefaultHelperRegistry): Array<HelperDoc & {
15
+ name: string;
16
+ aliases: string[];
17
+ }>;
@@ -0,0 +1,17 @@
1
+ import type { HelperDefinition, HelperDoc } from '../types.js';
2
+ import { DefaultHelperRegistry } from './registry.js';
3
+ export { DefaultHelperRegistry } from './registry.js';
4
+ export { escapeHtml, toDate, toNumber } from './format.js';
5
+ export { epcPayload, qrSvg } from './codes.js';
6
+ export { chartMarkup, chartPayload, imageMarkup } from './document.js';
7
+ export type { ChartSpec, ImageOptions } from './document.js';
8
+ /** Helpers shipped with the product (spec 05 §2), identical in all three engines. */
9
+ export declare const builtinHelpers: HelperDefinition[];
10
+ export declare function createHelperRegistry(extra?: HelperDefinition[]): DefaultHelperRegistry;
11
+ /** Lazily built default registry, shared by analyze() and the engines when none is supplied. */
12
+ export declare function defaultHelpers(): DefaultHelperRegistry;
13
+ /** Documentation entries that feed editor hover and completion (spec 05 §2). */
14
+ export declare function helperDocs(registry?: DefaultHelperRegistry): Array<HelperDoc & {
15
+ name: string;
16
+ aliases: string[];
17
+ }>;
@@ -0,0 +1,2 @@
1
+ import type { HelperDefinition } from '../types.cjs';
2
+ export declare const logicHelpers: HelperDefinition[];
@@ -0,0 +1,2 @@
1
+ import type { HelperDefinition } from '../types.js';
2
+ export declare const logicHelpers: HelperDefinition[];
@@ -0,0 +1,18 @@
1
+ import type { HelperContext, HelperDefinition, HelperRegistry } from '../types.cjs';
2
+ /** Registry of helpers shared by all engines (spec 05 §2). */
3
+ export declare class DefaultHelperRegistry implements HelperRegistry {
4
+ private readonly byName;
5
+ private readonly aliasToName;
6
+ constructor(definitions?: HelperDefinition[]);
7
+ get definitions(): ReadonlyMap<string, HelperDefinition>;
8
+ register(definition: HelperDefinition): void;
9
+ get(name: string): HelperDefinition | undefined;
10
+ has(name: string): boolean;
11
+ names(): string[];
12
+ /** The preferred name when `name` is a deprecated alias, otherwise undefined. */
13
+ deprecationOf(name: string): string | undefined;
14
+ bind(ctx: HelperContext): Map<string, {
15
+ fn: (...args: unknown[]) => unknown;
16
+ html: boolean;
17
+ }>;
18
+ }
@@ -0,0 +1,18 @@
1
+ import type { HelperContext, HelperDefinition, HelperRegistry } from '../types.js';
2
+ /** Registry of helpers shared by all engines (spec 05 §2). */
3
+ export declare class DefaultHelperRegistry implements HelperRegistry {
4
+ private readonly byName;
5
+ private readonly aliasToName;
6
+ constructor(definitions?: HelperDefinition[]);
7
+ get definitions(): ReadonlyMap<string, HelperDefinition>;
8
+ register(definition: HelperDefinition): void;
9
+ get(name: string): HelperDefinition | undefined;
10
+ has(name: string): boolean;
11
+ names(): string[];
12
+ /** The preferred name when `name` is a deprecated alias, otherwise undefined. */
13
+ deprecationOf(name: string): string | undefined;
14
+ bind(ctx: HelperContext): Map<string, {
15
+ fn: (...args: unknown[]) => unknown;
16
+ html: boolean;
17
+ }>;
18
+ }
@@ -0,0 +1,45 @@
1
+ import type { TemplateSettings } from '../assemble.cjs';
2
+ /**
3
+ * apitemplate.io importer (spec 10 §4, spec 05 §8): turns an exported template (HTML body, CSS,
4
+ * settings, sample JSON) into a Formfeed draft and a report of what needs attention. The Jinja2
5
+ * engine already carries the compatibility layer (string methods, `.items()`, filter aliases), so
6
+ * the source is kept verbatim apart from placeholders in header and footer templates.
7
+ */
8
+ export interface ApitemplateExport {
9
+ name?: string;
10
+ html: string;
11
+ css?: string;
12
+ /** apitemplate.io template settings (paper, margins, header/footer templates, image options). */
13
+ settings?: Record<string, unknown> | null;
14
+ /** Their sample JSON; a string is parsed. */
15
+ sample_data?: unknown;
16
+ /** Their template format; JPEG and PNG become image templates. */
17
+ format?: string | null;
18
+ }
19
+ export interface ImportNote {
20
+ code: string;
21
+ message: string;
22
+ line?: number;
23
+ column?: number;
24
+ /** Path under docs.formfeed.dev with the details. */
25
+ docs?: string;
26
+ }
27
+ export interface ImportResult {
28
+ name: string;
29
+ slug: string;
30
+ kind: 'pdf' | 'image';
31
+ engine: 'jinja2';
32
+ html: string;
33
+ css: string;
34
+ head: string;
35
+ settings: TemplateSettings;
36
+ sampleData: unknown;
37
+ /** Compile or analysis errors: the draft is created, but it will not render as is. */
38
+ errors: ImportNote[];
39
+ /** Unknown filters, unsupported Python constructs, ignored settings. */
40
+ warnings: ImportNote[];
41
+ /** What the importer changed or mapped. */
42
+ changes: string[];
43
+ }
44
+ export declare function slugFromName(name: string): string;
45
+ export declare function importApitemplate(input: ApitemplateExport): ImportResult;
@@ -0,0 +1,45 @@
1
+ import type { TemplateSettings } from '../assemble.js';
2
+ /**
3
+ * apitemplate.io importer (spec 10 §4, spec 05 §8): turns an exported template (HTML body, CSS,
4
+ * settings, sample JSON) into a Formfeed draft and a report of what needs attention. The Jinja2
5
+ * engine already carries the compatibility layer (string methods, `.items()`, filter aliases), so
6
+ * the source is kept verbatim apart from placeholders in header and footer templates.
7
+ */
8
+ export interface ApitemplateExport {
9
+ name?: string;
10
+ html: string;
11
+ css?: string;
12
+ /** apitemplate.io template settings (paper, margins, header/footer templates, image options). */
13
+ settings?: Record<string, unknown> | null;
14
+ /** Their sample JSON; a string is parsed. */
15
+ sample_data?: unknown;
16
+ /** Their template format; JPEG and PNG become image templates. */
17
+ format?: string | null;
18
+ }
19
+ export interface ImportNote {
20
+ code: string;
21
+ message: string;
22
+ line?: number;
23
+ column?: number;
24
+ /** Path under docs.formfeed.dev with the details. */
25
+ docs?: string;
26
+ }
27
+ export interface ImportResult {
28
+ name: string;
29
+ slug: string;
30
+ kind: 'pdf' | 'image';
31
+ engine: 'jinja2';
32
+ html: string;
33
+ css: string;
34
+ head: string;
35
+ settings: TemplateSettings;
36
+ sampleData: unknown;
37
+ /** Compile or analysis errors: the draft is created, but it will not render as is. */
38
+ errors: ImportNote[];
39
+ /** Unknown filters, unsupported Python constructs, ignored settings. */
40
+ warnings: ImportNote[];
41
+ /** What the importer changed or mapped. */
42
+ changes: string[];
43
+ }
44
+ export declare function slugFromName(name: string): string;
45
+ export declare function importApitemplate(input: ApitemplateExport): ImportResult;
@@ -0,0 +1,10 @@
1
+ import type { RenderLimits } from './types.cjs';
2
+ /** Limits of spec 05 §7 for a normal render. */
3
+ export declare const defaultLimits: RenderLimits;
4
+ /**
5
+ * Post-hoc enforcement for synchronous engines: the render-worker isolates runaway templates with a
6
+ * process timeout; here we reject oversized or overlong results so they never reach Chromium.
7
+ */
8
+ export declare function enforceLimits(output: string, startedAt: number, limits: RenderLimits): string;
9
+ /** Wraps a partial resolver so nested includes past `includeDepth` fail instead of recursing. */
10
+ export declare function depthGuard(resolve: (name: string) => string | undefined, includeDepth: number): (name: string) => string | undefined;
@@ -0,0 +1,10 @@
1
+ import type { RenderLimits } from './types.js';
2
+ /** Limits of spec 05 §7 for a normal render. */
3
+ export declare const defaultLimits: RenderLimits;
4
+ /**
5
+ * Post-hoc enforcement for synchronous engines: the render-worker isolates runaway templates with a
6
+ * process timeout; here we reject oversized or overlong results so they never reach Chromium.
7
+ */
8
+ export declare function enforceLimits(output: string, startedAt: number, limits: RenderLimits): string;
9
+ /** Wraps a partial resolver so nested includes past `includeDepth` fail instead of recursing. */
10
+ export declare function depthGuard(resolve: (name: string) => string | undefined, includeDepth: number): (name: string) => string | undefined;
@@ -0,0 +1,30 @@
1
+ import type { TemplateKind, TemplateSettings } from './assemble.cjs';
2
+ /**
3
+ * Preview documents shared by the web editor and `formfeed dev` (spec 06 §3, spec 15 §4): the
4
+ * assembled document of `renderVersion` wrapped for the screen. Flow mode shows one page-wide
5
+ * sheet with margins, header and footer emulated; paged mode lets Paged.js paginate with the
6
+ * `@page` rule of the print reset and turns header and footer into running elements.
7
+ */
8
+ export interface RenderedDraft {
9
+ document: string;
10
+ headerHtml?: string;
11
+ footerHtml?: string;
12
+ settings: TemplateSettings;
13
+ kind: TemplateKind;
14
+ }
15
+ export declare function paperOf(settings: TemplateSettings): {
16
+ width: string;
17
+ height: string;
18
+ };
19
+ /** Chromium's header/footer placeholders replaced for the screen. */
20
+ export declare const pageNumberSpans: (html: string, page: string, total: string) => string;
21
+ export declare function flowDocument(draft: RenderedDraft): string;
22
+ export interface PagedOptions {
23
+ /** URL of the Paged.js polyfill the frame can load. */
24
+ pagedScriptUrl: string;
25
+ }
26
+ /**
27
+ * Charts are drawn after pagination (Paged.js clones the content into pages and a cloned canvas is
28
+ * blank) and the page count is posted to the parent as `{ type: 'formfeed:pages', pages }`.
29
+ */
30
+ export declare function pagedDocument(draft: RenderedDraft, options: PagedOptions): string;
@@ -0,0 +1,30 @@
1
+ import type { TemplateKind, TemplateSettings } from './assemble.js';
2
+ /**
3
+ * Preview documents shared by the web editor and `formfeed dev` (spec 06 §3, spec 15 §4): the
4
+ * assembled document of `renderVersion` wrapped for the screen. Flow mode shows one page-wide
5
+ * sheet with margins, header and footer emulated; paged mode lets Paged.js paginate with the
6
+ * `@page` rule of the print reset and turns header and footer into running elements.
7
+ */
8
+ export interface RenderedDraft {
9
+ document: string;
10
+ headerHtml?: string;
11
+ footerHtml?: string;
12
+ settings: TemplateSettings;
13
+ kind: TemplateKind;
14
+ }
15
+ export declare function paperOf(settings: TemplateSettings): {
16
+ width: string;
17
+ height: string;
18
+ };
19
+ /** Chromium's header/footer placeholders replaced for the screen. */
20
+ export declare const pageNumberSpans: (html: string, page: string, total: string) => string;
21
+ export declare function flowDocument(draft: RenderedDraft): string;
22
+ export interface PagedOptions {
23
+ /** URL of the Paged.js polyfill the frame can load. */
24
+ pagedScriptUrl: string;
25
+ }
26
+ /**
27
+ * Charts are drawn after pagination (Paged.js clones the content into pages and a cloned canvas is
28
+ * blank) and the page count is posted to the parent as `{ type: 'formfeed:pages', pages }`.
29
+ */
30
+ export declare function pagedDocument(draft: RenderedDraft, options: PagedOptions): string;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * JSON Schema (draft 2020-12) inference from sample data (spec 05 §4). Arrays infer their item
3
+ * schema from every element; objects mark keys present in all samples as required.
4
+ */
5
+ export interface JsonSchema {
6
+ $schema?: string;
7
+ type?: string | string[];
8
+ properties?: Record<string, JsonSchema>;
9
+ required?: string[];
10
+ items?: JsonSchema;
11
+ examples?: unknown[];
12
+ format?: string;
13
+ additionalProperties?: boolean;
14
+ }
15
+ export declare function inferSchema(sample: unknown): JsonSchema;
16
+ /** Flat list of dotted paths with their types, for completion items in the editor. */
17
+ export declare function schemaPaths(schema: JsonSchema, prefix?: string[]): Array<{
18
+ path: string[];
19
+ type: string;
20
+ format?: string;
21
+ example?: unknown;
22
+ }>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * JSON Schema (draft 2020-12) inference from sample data (spec 05 §4). Arrays infer their item
3
+ * schema from every element; objects mark keys present in all samples as required.
4
+ */
5
+ export interface JsonSchema {
6
+ $schema?: string;
7
+ type?: string | string[];
8
+ properties?: Record<string, JsonSchema>;
9
+ required?: string[];
10
+ items?: JsonSchema;
11
+ examples?: unknown[];
12
+ format?: string;
13
+ additionalProperties?: boolean;
14
+ }
15
+ export declare function inferSchema(sample: unknown): JsonSchema;
16
+ /** Flat list of dotted paths with their types, for completion items in the editor. */
17
+ export declare function schemaPaths(schema: JsonSchema, prefix?: string[]): Array<{
18
+ path: string[];
19
+ type: string;
20
+ format?: string;
21
+ example?: unknown;
22
+ }>;
@@ -0,0 +1,22 @@
1
+ import type { EngineId } from './types.cjs';
2
+ /**
3
+ * Snippet library (spec 06 §2): building blocks in every engine's syntax with the sample data keys
4
+ * they expect, so inserting one also completes the data panel.
5
+ */
6
+ export interface Snippet {
7
+ id: string;
8
+ title: string;
9
+ description: string;
10
+ /** Source per engine; `$0` marks the final cursor position (Monaco snippet syntax). */
11
+ source: Record<EngineId, string>;
12
+ /** Sample data merged (deep, without overwriting) into the template's sample data. */
13
+ sampleData?: Record<string, unknown>;
14
+ /** CSS appended to the template's stylesheet if the selector prefix is absent. */
15
+ css?: string;
16
+ }
17
+ export declare const snippets: Snippet[];
18
+ export declare function snippetsFor(engine: EngineId): Array<Snippet & {
19
+ code: string;
20
+ }>;
21
+ /** Deep merge that keeps existing values; arrays are taken from the base when present. */
22
+ export declare function mergeSampleData(base: unknown, extra: Record<string, unknown> | undefined): unknown;
@@ -0,0 +1,22 @@
1
+ import type { EngineId } from './types.js';
2
+ /**
3
+ * Snippet library (spec 06 §2): building blocks in every engine's syntax with the sample data keys
4
+ * they expect, so inserting one also completes the data panel.
5
+ */
6
+ export interface Snippet {
7
+ id: string;
8
+ title: string;
9
+ description: string;
10
+ /** Source per engine; `$0` marks the final cursor position (Monaco snippet syntax). */
11
+ source: Record<EngineId, string>;
12
+ /** Sample data merged (deep, without overwriting) into the template's sample data. */
13
+ sampleData?: Record<string, unknown>;
14
+ /** CSS appended to the template's stylesheet if the selector prefix is absent. */
15
+ css?: string;
16
+ }
17
+ export declare const snippets: Snippet[];
18
+ export declare function snippetsFor(engine: EngineId): Array<Snippet & {
19
+ code: string;
20
+ }>;
21
+ /** Deep merge that keeps existing values; arrays are taken from the base when present. */
22
+ export declare function mergeSampleData(base: unknown, extra: Record<string, unknown> | undefined): unknown;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Shared contracts of `@formfeed/engine` (spec 05). Used by the Angular editor (browser) and the
3
+ * Fly.io render-worker (Node); nothing here may touch Node-only APIs.
4
+ */
5
+ export type EngineId = 'jinja2' | 'liquid' | 'handlebars';
6
+ /** 1-based line and column, end exclusive. */
7
+ export interface Position {
8
+ line: number;
9
+ column: number;
10
+ }
11
+ export interface Range {
12
+ start: Position;
13
+ end: Position;
14
+ }
15
+ export type DiagnosticSeverity = 'error' | 'warning' | 'info';
16
+ export interface Diagnostic {
17
+ severity: DiagnosticSeverity;
18
+ message: string;
19
+ range: Range;
20
+ /** Stable code the editor keys quick-fixes and docs on, e.g. `unknown-filter`. */
21
+ code: string;
22
+ /** Optional quick-fix: replace the range with `replacement`. */
23
+ fix?: {
24
+ title: string;
25
+ replacement: string;
26
+ };
27
+ }
28
+ export interface VariableRef {
29
+ path: string[];
30
+ range: Range;
31
+ kind: 'read' | 'loop-var' | 'assigned';
32
+ }
33
+ export interface FilterRef {
34
+ name: string;
35
+ range: Range;
36
+ known: boolean;
37
+ }
38
+ export interface IncludeRef {
39
+ name: string;
40
+ range: Range;
41
+ }
42
+ export interface BlockRef {
43
+ type: 'for' | 'if' | 'block' | 'raw' | 'macro' | 'other';
44
+ open: Range;
45
+ close?: Range;
46
+ }
47
+ export interface Analysis {
48
+ variables: VariableRef[];
49
+ filters: FilterRef[];
50
+ includes: IncludeRef[];
51
+ blocks: BlockRef[];
52
+ diagnostics: Diagnostic[];
53
+ }
54
+ export interface AnalyzeOptions {
55
+ /** Sample data; when given, variable paths missing from it produce warnings. */
56
+ sampleData?: unknown;
57
+ /** Helper registry that decides which filter names are known. Defaults to the built-in set. */
58
+ helpers?: HelperRegistry;
59
+ }
60
+ /** Resolves a workspace partial by name to its source; `undefined` when it does not exist. */
61
+ export type PartialResolver = (name: string) => string | undefined;
62
+ export interface RenderLimits {
63
+ /** Wall-clock budget of one render in milliseconds. */
64
+ ms: number;
65
+ /** Maximum size of the rendered HTML in bytes. */
66
+ outputBytes: number;
67
+ /** Maximum nesting of includes and extends. */
68
+ includeDepth: number;
69
+ }
70
+ export interface RenderContext {
71
+ locale: string;
72
+ timezone: string;
73
+ currency: string;
74
+ partials: PartialResolver;
75
+ helpers: HelperRegistry;
76
+ limits: RenderLimits;
77
+ /** Template-level dictionaries for `t()`: locale → key → text (USP-8). */
78
+ i18n?: Record<string, Record<string, string>>;
79
+ /** Base URL for `asset()` (workspace asset root on the CDN). */
80
+ assetBaseUrl?: string;
81
+ }
82
+ export interface CompileOptions {
83
+ /** Name used in error messages and as the root include name. */
84
+ name?: string;
85
+ }
86
+ export interface CompiledTemplate {
87
+ engine: EngineId;
88
+ name: string;
89
+ source: string;
90
+ render(data: unknown, ctx: RenderContext): Promise<string>;
91
+ }
92
+ export interface Engine {
93
+ id: EngineId;
94
+ /** Parses and compiles; throws `EngineSyntaxError` with line and column. */
95
+ compile(source: string, opts?: CompileOptions): CompiledTemplate;
96
+ analyze(source: string, opts?: AnalyzeOptions): Analysis;
97
+ render(tpl: CompiledTemplate, data: unknown, ctx: RenderContext): Promise<string>;
98
+ }
99
+ export type HelperCategory = 'format' | 'text' | 'collection' | 'logic' | 'code' | 'document' | 'debug';
100
+ export interface HelperDoc {
101
+ signature: string;
102
+ description: string;
103
+ example: string;
104
+ category: HelperCategory;
105
+ }
106
+ export interface HelperDefinition {
107
+ name: string;
108
+ /** Other names the helper answers to (apitemplate.io compatibility aliases). */
109
+ aliases?: string[];
110
+ /** Result is HTML that must not be escaped by the engine. */
111
+ html?: boolean;
112
+ /** Emits a warning diagnostic pointing to the preferred name (deprecated aliases). */
113
+ deprecatedAlias?: Record<string, string>;
114
+ doc: HelperDoc;
115
+ /** Receives the render context first, then the template arguments. */
116
+ fn: (ctx: HelperContext, ...args: unknown[]) => unknown;
117
+ }
118
+ /** What helpers know about the current render without receiving the whole context. */
119
+ export interface HelperContext {
120
+ locale: string;
121
+ timezone: string;
122
+ currency: string;
123
+ i18n?: Record<string, Record<string, string>>;
124
+ assetBaseUrl?: string;
125
+ }
126
+ export interface HelperRegistry {
127
+ /** Registered helpers keyed by canonical name. */
128
+ readonly definitions: ReadonlyMap<string, HelperDefinition>;
129
+ /** Resolves a name or alias. */
130
+ get(name: string): HelperDefinition | undefined;
131
+ has(name: string): boolean;
132
+ /** Every name the engines know, aliases included. */
133
+ names(): string[];
134
+ /** Concrete functions bound to a render context, keyed by every name and alias. */
135
+ bind(ctx: HelperContext): Map<string, {
136
+ fn: (...args: unknown[]) => unknown;
137
+ html: boolean;
138
+ }>;
139
+ register(definition: HelperDefinition): void;
140
+ }