vellora 0.1.0-alpha.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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +28 -0
  3. package/dist/errors.d.ts +74 -0
  4. package/dist/errors.js +105 -0
  5. package/dist/errors.js.map +1 -0
  6. package/dist/index.d.ts +19 -0
  7. package/dist/index.js +19 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/input.d.ts +6 -0
  10. package/dist/input.js +79 -0
  11. package/dist/input.js.map +1 -0
  12. package/dist/mock-bridge.d.ts +15 -0
  13. package/dist/mock-bridge.js +0 -0
  14. package/dist/mock-bridge.js.map +1 -0
  15. package/dist/native-bridge.d.ts +6 -0
  16. package/dist/native-bridge.js +43 -0
  17. package/dist/native-bridge.js.map +1 -0
  18. package/dist/orchestrate.d.ts +17 -0
  19. package/dist/orchestrate.js +148 -0
  20. package/dist/orchestrate.js.map +1 -0
  21. package/dist/render.d.ts +30 -0
  22. package/dist/render.js +82 -0
  23. package/dist/render.js.map +1 -0
  24. package/dist/source-position.d.ts +10 -0
  25. package/dist/source-position.js +21 -0
  26. package/dist/source-position.js.map +1 -0
  27. package/dist/template/helpers.d.ts +7 -0
  28. package/dist/template/helpers.js +150 -0
  29. package/dist/template/helpers.js.map +1 -0
  30. package/dist/template/index.d.ts +11 -0
  31. package/dist/template/index.js +11 -0
  32. package/dist/template/index.js.map +1 -0
  33. package/dist/template/interpreter.d.ts +4 -0
  34. package/dist/template/interpreter.js +208 -0
  35. package/dist/template/interpreter.js.map +1 -0
  36. package/dist/template/parser.d.ts +22 -0
  37. package/dist/template/parser.js +99 -0
  38. package/dist/template/parser.js.map +1 -0
  39. package/dist/template/tokenizer.d.ts +22 -0
  40. package/dist/template/tokenizer.js +74 -0
  41. package/dist/template/tokenizer.js.map +1 -0
  42. package/dist/types.d.ts +70 -0
  43. package/dist/types.js +2 -0
  44. package/dist/types.js.map +1 -0
  45. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Diego Malta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # vellora
2
+
3
+ HTML to PDF for Node.js — no browser. Public API + templating + strict orchestration.
4
+
5
+ > Part of the [vellora](https://github.com/diomalta/vellora) project.
6
+ > **Pre-release (alpha)** — the API may change before `1.0`.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install vellora
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```ts
17
+ import { renderPdf } from "vellora";
18
+
19
+ const pdf = await renderPdf(html, data);
20
+ // pdf: Uint8Array — page size comes from CSS in your HTML: @page { size: A4 }
21
+ ```
22
+
23
+ See the [project README](https://github.com/diomalta/vellora#readme) for the full
24
+ guide, compatibility table, and roadmap.
25
+
26
+ ## License
27
+
28
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Typed error contract for the public `vellora` API.
3
+ *
4
+ * Every failure path of `renderPdf` / `renderPdfToStream` rejects with one of these typed errors
5
+ * (never a bare `Error`). `VelloraError` is the base; consumers branch on `instanceof` and on the
6
+ * stable, machine-readable `code`. `VelloraUnsupportedError` surfaces the core's located
7
+ * out-of-subset diagnostic verbatim — node location and remediation hint — and is the single
8
+ * contract shape mapped from the bridge in `unsupportedFromDiagnostic`.
9
+ */
10
+ /** Stable, machine-readable error codes. Consumers and CI branch on these. */
11
+ export type VelloraErrorCode = "VELLORA_ERROR" | "VELLORA_TEMPLATE_ERROR" | "VELLORA_INPUT_ERROR" | "VELLORA_UNSUPPORTED";
12
+ /** Base class for every error thrown by the public API. */
13
+ export declare class VelloraError extends Error {
14
+ /** Stable, machine-readable discriminator. */
15
+ readonly code: VelloraErrorCode;
16
+ constructor(message: string, code?: VelloraErrorCode);
17
+ }
18
+ /** A template syntax/semantic error (unclosed block, unknown helper). Carries token location. */
19
+ export declare class VelloraTemplateError extends VelloraError {
20
+ /** 1-based line of the offending token, when known. */
21
+ readonly line?: number;
22
+ /** 1-based column of the offending token, when known. */
23
+ readonly col?: number;
24
+ constructor(message: string, location?: {
25
+ line: number;
26
+ col: number;
27
+ });
28
+ }
29
+ /** Bad input to the public API (wrong `html` type, or a `Readable` that errored). */
30
+ export declare class VelloraInputError extends VelloraError {
31
+ constructor(message: string, options?: {
32
+ cause?: unknown;
33
+ });
34
+ }
35
+ /**
36
+ * The located out-of-subset diagnostic the bridge/core emits, flattened across the FFI. This exact
37
+ * shape is the contract with the napi binding.
38
+ */
39
+ export interface UnsupportedDiagnostic {
40
+ /** The out-of-subset feature in the core's colon-namespaced taxonomy, e.g. `"css:animation"` or
41
+ * `"element:script"`. Best-effort mode normalizes `@vellora/lint` rule ids to this same vocabulary,
42
+ * so a given construct yields one canonical `feature` regardless of which path reported it. */
43
+ feature: string;
44
+ /** 1-based source line of the offending node, or `null` when the source position is unknown. */
45
+ line: number | null;
46
+ /** 1-based source column of the offending node, or `null` when the source position is unknown. */
47
+ col: number | null;
48
+ /** Remediation hint from the core, e.g. `"run vellora fix"`. */
49
+ hint: string;
50
+ }
51
+ /**
52
+ * An out-of-subset construct reported by the core. Carries the located diagnostic verbatim so
53
+ * consumers and CI can surface a precise, actionable message and branch on it.
54
+ */
55
+ export declare class VelloraUnsupportedError extends VelloraError {
56
+ /** The out-of-subset feature reported by the core. */
57
+ readonly feature: string;
58
+ /** 1-based source line of the offending node, or `null` when the source position is unknown. */
59
+ readonly line: number | null;
60
+ /** 1-based source column of the offending node, or `null` when the source position is unknown. */
61
+ readonly col: number | null;
62
+ /** Remediation hint from the core. */
63
+ readonly hint: string;
64
+ constructor(diagnostic: UnsupportedDiagnostic);
65
+ }
66
+ /** Type guard: does an unknown value carry the structured located diagnostic fields? */
67
+ export declare function isUnsupportedDiagnostic(value: unknown): value is UnsupportedDiagnostic;
68
+ /**
69
+ * Adapter (the single seam): reconstruct a `VelloraUnsupportedError` from a core/native
70
+ * located diagnostic, preserving node location + remediation hint verbatim. The bridge may reject
71
+ * either with a `VelloraUnsupportedError` already, or with an error/object carrying the structured
72
+ * `{ feature, line, col, hint }` fields; both map to the same typed error.
73
+ */
74
+ export declare function unsupportedFromDiagnostic(reason: unknown): VelloraUnsupportedError | undefined;
package/dist/errors.js ADDED
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Typed error contract for the public `vellora` API.
3
+ *
4
+ * Every failure path of `renderPdf` / `renderPdfToStream` rejects with one of these typed errors
5
+ * (never a bare `Error`). `VelloraError` is the base; consumers branch on `instanceof` and on the
6
+ * stable, machine-readable `code`. `VelloraUnsupportedError` surfaces the core's located
7
+ * out-of-subset diagnostic verbatim — node location and remediation hint — and is the single
8
+ * contract shape mapped from the bridge in `unsupportedFromDiagnostic`.
9
+ */
10
+ /** Base class for every error thrown by the public API. */
11
+ export class VelloraError extends Error {
12
+ /** Stable, machine-readable discriminator. */
13
+ code;
14
+ constructor(message, code = "VELLORA_ERROR") {
15
+ super(message);
16
+ this.name = "VelloraError";
17
+ this.code = code;
18
+ }
19
+ }
20
+ /** A template syntax/semantic error (unclosed block, unknown helper). Carries token location. */
21
+ export class VelloraTemplateError extends VelloraError {
22
+ /** 1-based line of the offending token, when known. */
23
+ line;
24
+ /** 1-based column of the offending token, when known. */
25
+ col;
26
+ constructor(message, location) {
27
+ super(message, "VELLORA_TEMPLATE_ERROR");
28
+ this.name = "VelloraTemplateError";
29
+ this.line = location?.line;
30
+ this.col = location?.col;
31
+ }
32
+ }
33
+ /** Bad input to the public API (wrong `html` type, or a `Readable` that errored). */
34
+ export class VelloraInputError extends VelloraError {
35
+ constructor(message, options) {
36
+ super(message, "VELLORA_INPUT_ERROR");
37
+ this.name = "VelloraInputError";
38
+ if (options && "cause" in options) {
39
+ this.cause = options.cause;
40
+ }
41
+ }
42
+ }
43
+ /**
44
+ * An out-of-subset construct reported by the core. Carries the located diagnostic verbatim so
45
+ * consumers and CI can surface a precise, actionable message and branch on it.
46
+ */
47
+ export class VelloraUnsupportedError extends VelloraError {
48
+ /** The out-of-subset feature reported by the core. */
49
+ feature;
50
+ /** 1-based source line of the offending node, or `null` when the source position is unknown. */
51
+ line;
52
+ /** 1-based source column of the offending node, or `null` when the source position is unknown. */
53
+ col;
54
+ /** Remediation hint from the core. */
55
+ hint;
56
+ constructor(diagnostic) {
57
+ super(formatUnsupportedMessage(diagnostic), "VELLORA_UNSUPPORTED");
58
+ this.name = "VelloraUnsupportedError";
59
+ this.feature = diagnostic.feature;
60
+ this.line = diagnostic.line;
61
+ this.col = diagnostic.col;
62
+ this.hint = diagnostic.hint;
63
+ }
64
+ }
65
+ /** Render the located-diagnostic message, omitting the location when line/col are unknown (null). */
66
+ function formatUnsupportedMessage(diagnostic) {
67
+ const location = diagnostic.line !== null && diagnostic.col !== null
68
+ ? ` at line ${diagnostic.line}, column ${diagnostic.col}`
69
+ : "";
70
+ return `Unsupported construct "${diagnostic.feature}"${location}: ${diagnostic.hint}`;
71
+ }
72
+ /** Type guard: does an unknown value carry the structured located diagnostic fields? */
73
+ export function isUnsupportedDiagnostic(value) {
74
+ if (typeof value !== "object" || value === null) {
75
+ return false;
76
+ }
77
+ const v = value;
78
+ return (typeof v.feature === "string" &&
79
+ (typeof v.line === "number" || v.line === null) &&
80
+ (typeof v.col === "number" || v.col === null) &&
81
+ typeof v.hint === "string");
82
+ }
83
+ /**
84
+ * Adapter (the single seam): reconstruct a `VelloraUnsupportedError` from a core/native
85
+ * located diagnostic, preserving node location + remediation hint verbatim. The bridge may reject
86
+ * either with a `VelloraUnsupportedError` already, or with an error/object carrying the structured
87
+ * `{ feature, line, col, hint }` fields; both map to the same typed error.
88
+ */
89
+ export function unsupportedFromDiagnostic(reason) {
90
+ if (reason instanceof VelloraUnsupportedError) {
91
+ return reason;
92
+ }
93
+ if (isUnsupportedDiagnostic(reason)) {
94
+ return new VelloraUnsupportedError(reason);
95
+ }
96
+ // The error may carry the diagnostic on a nested property (FFI flattening).
97
+ if (typeof reason === "object" && reason !== null) {
98
+ const diagnostic = reason.diagnostic;
99
+ if (isUnsupportedDiagnostic(diagnostic)) {
100
+ return new VelloraUnsupportedError(diagnostic);
101
+ }
102
+ }
103
+ return undefined;
104
+ }
105
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * vellora — public API.
3
+ *
4
+ * Pass document HTML (and optional data) in, get a deterministic PDF out, strict by default.
5
+ *
6
+ * import { renderPdf } from "vellora";
7
+ * const pdf = await renderPdf(html, data);
8
+ *
9
+ * Input is always **content** (string | Uint8Array | Readable), never a file path. Templating
10
+ * (`{{ var }}`, `{% for %}`, `{% if %}`, `currency`/`number`/`date` helpers) runs before render.
11
+ * Strict mode validates and never mutates; `{ strict: false }` runs `@vellora/lint` fixers first.
12
+ */
13
+ export { isUnsupportedDiagnostic, unsupportedFromDiagnostic, type UnsupportedDiagnostic, VelloraError, type VelloraErrorCode, VelloraInputError, VelloraTemplateError, VelloraUnsupportedError, } from "./errors.js";
14
+ export { MockNativeBridge, type MockRenderCall } from "./mock-bridge.js";
15
+ export { NativeAddonBridge } from "./native-bridge.js";
16
+ export { DEFAULT_CREATION_DATE, resolveOptions } from "./orchestrate.js";
17
+ export { renderPdf, renderPdfToStream, setNativeBridge } from "./render.js";
18
+ export { renderTemplate } from "./template/index.js";
19
+ export type { BridgeRenderOptions, HtmlInput, NativeBridge, RenderData, RenderMetadata, RenderOptions, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * vellora — public API.
3
+ *
4
+ * Pass document HTML (and optional data) in, get a deterministic PDF out, strict by default.
5
+ *
6
+ * import { renderPdf } from "vellora";
7
+ * const pdf = await renderPdf(html, data);
8
+ *
9
+ * Input is always **content** (string | Uint8Array | Readable), never a file path. Templating
10
+ * (`{{ var }}`, `{% for %}`, `{% if %}`, `currency`/`number`/`date` helpers) runs before render.
11
+ * Strict mode validates and never mutates; `{ strict: false }` runs `@vellora/lint` fixers first.
12
+ */
13
+ export { isUnsupportedDiagnostic, unsupportedFromDiagnostic, VelloraError, VelloraInputError, VelloraTemplateError, VelloraUnsupportedError, } from "./errors.js";
14
+ export { MockNativeBridge } from "./mock-bridge.js";
15
+ export { NativeAddonBridge } from "./native-bridge.js";
16
+ export { DEFAULT_CREATION_DATE, resolveOptions } from "./orchestrate.js";
17
+ export { renderPdf, renderPdfToStream, setNativeBridge } from "./render.js";
18
+ export { renderTemplate } from "./template/index.js";
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EAEzB,YAAY,EAEZ,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAuB,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { HtmlInput } from "./types.js";
2
+ /**
3
+ * Normalize `html` to a content string. No filesystem access; a path-like string is content.
4
+ * Async because a `Readable` must be drained before templating begins.
5
+ */
6
+ export declare function normalizeInput(html: HtmlInput): Promise<string>;
package/dist/input.js ADDED
@@ -0,0 +1,79 @@
1
+ import { VelloraInputError } from "./errors.js";
2
+ const UTF8 = new TextDecoder("utf-8", { fatal: true });
3
+ /**
4
+ * Decode bytes as strict UTF-8. The native core rejects non-UTF-8 input, so the public byte path
5
+ * must too: a `fatal` decoder throws on malformed bytes, which we map to a `VelloraInputError`
6
+ * rather than silently substituting U+FFFD (which would re-encode as valid UTF-8 and hide the bug).
7
+ */
8
+ function decodeUtf8(bytes) {
9
+ try {
10
+ return UTF8.decode(bytes);
11
+ }
12
+ catch (cause) {
13
+ throw new VelloraInputError("The input HTML is not valid UTF-8.", { cause });
14
+ }
15
+ }
16
+ /** Duck-typed `Readable` check (avoids `instanceof` coupling across realms). */
17
+ function isReadable(value) {
18
+ return (typeof value === "object" &&
19
+ value !== null &&
20
+ typeof value.pipe === "function" &&
21
+ typeof value.on === "function");
22
+ }
23
+ /** Buffer a `Readable` to completion, then decode as UTF-8. Rejects (never hangs) on stream error. */
24
+ function bufferReadable(stream) {
25
+ return new Promise((resolve, reject) => {
26
+ const chunks = [];
27
+ const onError = (cause) => {
28
+ cleanup();
29
+ reject(new VelloraInputError("The input HTML stream errored before it ended.", { cause }));
30
+ };
31
+ const onData = (chunk) => {
32
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk);
33
+ };
34
+ const onEnd = () => {
35
+ cleanup();
36
+ try {
37
+ resolve(decodeUtf8(Buffer.concat(chunks)));
38
+ }
39
+ catch (err) {
40
+ reject(err);
41
+ }
42
+ };
43
+ const cleanup = () => {
44
+ stream.off("data", onData);
45
+ stream.off("end", onEnd);
46
+ stream.off("error", onError);
47
+ stream.off("close", onClose);
48
+ };
49
+ const onClose = () => {
50
+ // A destroy() without an `error` still closes; treat a close-before-end as an error so the
51
+ // promise never hangs waiting for an `end` that will not arrive.
52
+ cleanup();
53
+ reject(new VelloraInputError("The input HTML stream closed before it ended.", {
54
+ cause: undefined,
55
+ }));
56
+ };
57
+ stream.on("data", onData);
58
+ stream.once("end", onEnd);
59
+ stream.once("error", onError);
60
+ stream.once("close", onClose);
61
+ });
62
+ }
63
+ /**
64
+ * Normalize `html` to a content string. No filesystem access; a path-like string is content.
65
+ * Async because a `Readable` must be drained before templating begins.
66
+ */
67
+ export async function normalizeInput(html) {
68
+ if (typeof html === "string") {
69
+ return html;
70
+ }
71
+ if (html instanceof Uint8Array) {
72
+ return decodeUtf8(html);
73
+ }
74
+ if (isReadable(html)) {
75
+ return bufferReadable(html);
76
+ }
77
+ throw new VelloraInputError("Invalid html input: expected a string, Uint8Array, or Readable (content, never a file path).");
78
+ }
79
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.js","sourceRoot":"","sources":["../src/input.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { BridgeRenderOptions, NativeBridge } from "./types.js";
2
+ /** One record of a `render` call, captured for test assertions. */
3
+ export interface MockRenderCall {
4
+ html: string;
5
+ options: BridgeRenderOptions;
6
+ }
7
+ /**
8
+ * A deterministic mock implementing the `NativeBridge` contract. The bytes are a stable function of
9
+ * `(html, options)`, so identical inputs render byte-identically and different inputs differ.
10
+ */
11
+ export declare class MockNativeBridge implements NativeBridge {
12
+ /** Every `render` call, in order, for assertions. */
13
+ readonly calls: MockRenderCall[];
14
+ render(html: string, options: BridgeRenderOptions): Promise<Uint8Array>;
15
+ }
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-bridge.js","sourceRoot":"","sources":["../src/mock-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAgBvD;;;;;;;;GAQG;AACH,MAAM,iBAAiB,GAAsB;IAC3C,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE;IAC/E,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE;IAC/E,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE;CACjF,CAAC;AAEF,6FAA6F;AAC7F,SAAS,aAAa,CAAC,IAAY;IACjC,KAAK,MAAM,SAAS,IAAI,iBAAiB,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YACzD,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC;QACzE,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,gBAAgB;IAC3B,qDAAqD;IAC5C,KAAK,GAAqB,EAAE,CAAC;IAEtC,MAAM,CAAC,IAAY,EAAE,OAA4B;QAC/C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACnC,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,CAAC;CACF;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,IAAY,EAAE,OAA4B;IAC9D,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC;SAChC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC;SACpB,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC;SACnB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;SAChD,MAAM,CAAC,KAAK,CAAC,CAAC;IACjB,MAAM,IAAI,GAAG;QACX,UAAU;QACV,SAAS;QACT,mCAAmC;QACnC,QAAQ;QACR,SAAS;QACT,2CAA2C;QAC3C,QAAQ;QACR,SAAS;QACT,iCAAiC;QACjC,QAAQ;QACR,kBAAkB,OAAO,CAAC,QAAQ,CAAC,YAAY,EAAE;QACjD,aAAa,MAAM,EAAE;QACrB,OAAO;KACR,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { BridgeRenderOptions, NativeBridge } from "./types.js";
2
+ export declare class NativeAddonBridge implements NativeBridge {
3
+ private renderFn?;
4
+ private load;
5
+ render(html: string, options: BridgeRenderOptions): Promise<Uint8Array>;
6
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Real native bridge: adapts `@vellora/native` (the prebuilt napi-rs addon) to the `NativeBridge`
3
+ * contract the orchestration calls. This is the production default; the mock backs unit tests.
4
+ *
5
+ * The addon is imported lazily on first render, so importing `vellora` never loads the native `.node`
6
+ * until a real render happens (tests that inject their own bridge never trigger it).
7
+ */
8
+ import { VelloraInputError } from "./errors.js";
9
+ /**
10
+ * Map the orchestration's ISO-8601 creation date to the addon's `[year, month, day]` (UTC).
11
+ *
12
+ * Currently records the PDF creation date at **date granularity (year, month, day) in UTC** — the
13
+ * `vellora-core` PDF writer only accepts y/m/d — so the time-of-day and timezone of the ISO-8601
14
+ * instant are intentionally dropped here (documented, not silent). The public boundary
15
+ * (`orchestrate.resolveOptions`) rejects an unparseable date before we get here; the `Number.isNaN`
16
+ * guard below is defense-in-depth so a `NaN` triple can never be forwarded across the FFI.
17
+ */
18
+ function isoToYmd(iso) {
19
+ const d = new Date(iso);
20
+ if (Number.isNaN(d.getTime())) {
21
+ throw new VelloraInputError(`metadata.creationDate must be a valid ISO-8601 date string; received ${JSON.stringify(iso)}.`);
22
+ }
23
+ return [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()];
24
+ }
25
+ export class NativeAddonBridge {
26
+ renderFn;
27
+ async load() {
28
+ if (!this.renderFn) {
29
+ const native = (await import("@vellora/native"));
30
+ this.renderFn = native.render;
31
+ }
32
+ return this.renderFn;
33
+ }
34
+ async render(html, options) {
35
+ const render = await this.load();
36
+ const bytes = new TextEncoder().encode(html);
37
+ return render(bytes, {
38
+ title: options.metadata.title,
39
+ creationDate: isoToYmd(options.metadata.creationDate),
40
+ });
41
+ }
42
+ }
43
+ //# sourceMappingURL=native-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-bridge.js","sourceRoot":"","sources":["../src/native-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAShD;;;;;;;;GAQG;AACH,SAAS,QAAQ,CAAC,GAAW;IAC3B,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,iBAAiB,CACzB,wEAAwE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAC/F,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,OAAO,iBAAiB;IACpB,QAAQ,CAAe;IAEvB,KAAK,CAAC,IAAI;QAChB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAA4B,CAAC;YAC5E,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC;QAChC,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAY,EAAE,OAA4B;QACrD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC7C,OAAO,MAAM,CAAC,KAAK,EAAE;YACnB,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK;YAC7B,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;SACtD,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,17 @@
1
+ import type { BridgeRenderOptions, NativeBridge, RenderOptions } from "./types.js";
2
+ /**
3
+ * Fixed default PDF creation date injected when `opts.metadata.creationDate` is omitted. A constant
4
+ * (the project's reference instant), never the host wall-clock, so output is byte-stable. This is
5
+ * what satisfies the `vellora-core` `pdf-output` "Deterministic creation date" precondition.
6
+ */
7
+ export declare const DEFAULT_CREATION_DATE = "2000-01-01T00:00:00.000Z";
8
+ /** Resolve public `RenderOptions` into the fully-resolved config handed to the bridge. */
9
+ export declare function resolveOptions(opts?: RenderOptions): BridgeRenderOptions;
10
+ /**
11
+ * Drive validation + native render for already-templated HTML.
12
+ *
13
+ * @param html finalized HTML from the templating engine.
14
+ * @param opts public render options (default strict).
15
+ * @param bridge the swappable native render boundary.
16
+ */
17
+ export declare function orchestrate(html: string, opts: RenderOptions, bridge: NativeBridge): Promise<Uint8Array>;
@@ -0,0 +1,148 @@
1
+ import { VelloraError, VelloraInputError, VelloraUnsupportedError, unsupportedFromDiagnostic, } from "./errors.js";
2
+ /**
3
+ * Map a `@vellora/lint` `RuleId` (kebab-case) onto the SAME colon-namespaced `feature` taxonomy the
4
+ * `vellora-core` strict path emits (`element:<tag>`, `css:<feature>`), so the typed
5
+ * `VelloraUnsupportedError.feature` is identical for a given out-of-subset construct whether it is
6
+ * surfaced via best-effort lint or via the strict/core gate. The `{ feature, line, col, hint }`
7
+ * shape is a single contract; consumers/CI key on `feature`, so the two paths must not
8
+ * diverge.
9
+ */
10
+ const RULE_ID_TO_CORE_FEATURE = {
11
+ "script-element": "element:script",
12
+ "inline-svg": "element:svg",
13
+ "css-animation": "css:animation",
14
+ "flex-grid-in-td": "css:grid",
15
+ "img-dimension-attrs": "css:img-dimensions",
16
+ "invalid-markup": "html:invalid-markup",
17
+ };
18
+ /**
19
+ * Fixed default PDF creation date injected when `opts.metadata.creationDate` is omitted. A constant
20
+ * (the project's reference instant), never the host wall-clock, so output is byte-stable. This is
21
+ * what satisfies the `vellora-core` `pdf-output` "Deterministic creation date" precondition.
22
+ */
23
+ export const DEFAULT_CREATION_DATE = "2000-01-01T00:00:00.000Z";
24
+ /** Inclusive UTC-year range accepted at the public boundary. The napi FFI maps the year to a `u16`
25
+ * (`[year, month, day]`), so a year outside `1..=65535` would otherwise pass public validation yet be
26
+ * silently dropped at the FFI checked-conversion (extended-ISO years run to +275760). Rejecting it
27
+ * here keeps the two validation layers in agreement and turns the failure into a typed
28
+ * `VelloraInputError` instead of a silent FFI drop. */
29
+ const MIN_CREATION_YEAR = 1;
30
+ const MAX_CREATION_YEAR = 65535;
31
+ /**
32
+ * Validate a caller-supplied `creationDate` at the public boundary. The string must be a
33
+ * `Date`-parseable ISO-8601 instant whose UTC year fits the FFI `u16`; an empty, non-parseable, or
34
+ * out-of-`u16`-range value rejects with `VelloraInputError` rather than being forwarded as a `NaN`
35
+ * date (which the core would otherwise coerce to a silently-wrong year-0 date) or silently dropped at
36
+ * the FFI.
37
+ */
38
+ function validateCreationDate(creationDate) {
39
+ const date = new Date(creationDate);
40
+ if (creationDate.trim() === "" || Number.isNaN(date.getTime())) {
41
+ throw new VelloraInputError(`metadata.creationDate must be a valid ISO-8601 date string; received ${JSON.stringify(creationDate)}.`);
42
+ }
43
+ const year = date.getUTCFullYear();
44
+ if (year < MIN_CREATION_YEAR || year > MAX_CREATION_YEAR) {
45
+ throw new VelloraInputError(`metadata.creationDate year must be in ${MIN_CREATION_YEAR}..=${MAX_CREATION_YEAR}; received year ${year} from ${JSON.stringify(creationDate)}.`);
46
+ }
47
+ }
48
+ /** Resolve public `RenderOptions` into the fully-resolved config handed to the bridge. */
49
+ export function resolveOptions(opts = {}) {
50
+ const metadata = opts.metadata ?? {};
51
+ if (metadata.creationDate !== undefined) {
52
+ validateCreationDate(metadata.creationDate);
53
+ }
54
+ const resolved = {
55
+ metadata: {
56
+ ...metadata,
57
+ creationDate: metadata.creationDate ?? DEFAULT_CREATION_DATE,
58
+ },
59
+ };
60
+ if ("fonts" in opts) {
61
+ resolved.fonts = opts.fonts;
62
+ }
63
+ if ("images" in opts) {
64
+ resolved.images = opts.images;
65
+ }
66
+ if (opts.baseUrl !== undefined) {
67
+ resolved.baseUrl = opts.baseUrl;
68
+ }
69
+ return resolved;
70
+ }
71
+ /**
72
+ * Best-effort fix: lazily import `@vellora/lint` (only here) and run its fixers on the HTML.
73
+ *
74
+ * `@vellora/lint` is a declared dependency that exports `fix`, so a missing `fix` indicates a real
75
+ * resolution/version/config failure of the requested best-effort operation — fail loudly instead of
76
+ * silently rendering the un-fixed HTML. It must also return the current `{ html, report }`
77
+ * shape; an old/version-skewed `fix` that resolves but returns the old `{ html }` shape would
78
+ * otherwise throw an opaque `TypeError` on `report.findings`, so its return is shape-guarded with the
79
+ * same loud message. The fixer also already computes the residual, non-auto-fixable findings;
80
+ * rather than discard them and rely on a later core gate, surface the first error-severity finding as
81
+ * the same typed `VelloraUnsupportedError` the core path uses, normalized to the core `feature`
82
+ * taxonomy.
83
+ */
84
+ async function applyFixers(html) {
85
+ const lint = await import("@vellora/lint");
86
+ const fix = lint.fix;
87
+ if (typeof fix !== "function") {
88
+ throw new VelloraError("best-effort mode requested but @vellora/lint.fix is unavailable");
89
+ }
90
+ const result = await fix(html);
91
+ // Guard the return SHAPE before dereferencing `report.findings`: the static cast above makes the
92
+ // structural fields look total to tsc, but a mis-resolved/old-version `fix` could resolve and
93
+ // return a different runtime shape. Fail loudly with a meaningful message instead of leaking a raw
94
+ // `TypeError: Cannot read properties of undefined (reading 'findings')`.
95
+ if (!result ||
96
+ typeof result.html !== "string" ||
97
+ !result.report ||
98
+ !Array.isArray(result.report.findings)) {
99
+ throw new VelloraError("best-effort mode requested but @vellora/lint.fix returned an unexpected shape");
100
+ }
101
+ // The fixer already located every remaining, non-auto-fixable diagnostic. Surface the first
102
+ // error-severity one as the typed error contract rather than dropping it. Normalize
103
+ // the lint `RuleId` to the core `feature` taxonomy so the best-effort and strict/core paths emit the
104
+ // IDENTICAL `feature` for the same construct. The lint `location` is in the FIXED/re-serialized
105
+ // output coordinate space (see `@vellora/lint.fix`), but the caller holds the ORIGINAL HTML and the
106
+ // orchestrator has no mapping back, so report `null` line/col rather than a coordinate that may point
107
+ // into the rewritten document — matching the core path's honest-None behavior.
108
+ const unfixable = result.report.findings.find((f) => f.severity === "error" && !f.applied);
109
+ if (unfixable) {
110
+ throw new VelloraUnsupportedError({
111
+ feature: RULE_ID_TO_CORE_FEATURE[unfixable.rule] ?? unfixable.rule,
112
+ line: null,
113
+ col: null,
114
+ hint: unfixable.suggestedFix,
115
+ });
116
+ }
117
+ return result.html;
118
+ }
119
+ /**
120
+ * Drive validation + native render for already-templated HTML.
121
+ *
122
+ * @param html finalized HTML from the templating engine.
123
+ * @param opts public render options (default strict).
124
+ * @param bridge the swappable native render boundary.
125
+ */
126
+ export async function orchestrate(html, opts, bridge) {
127
+ const resolved = resolveOptions(opts);
128
+ const strict = opts.strict !== false;
129
+ try {
130
+ // `applyFixers` runs inside the try so any fixer crash (e.g. a resvg rasterize failure) is
131
+ // mapped to a typed Vellora error instead of escaping as a bare `Error`.
132
+ const finalHtml = strict ? html : await applyFixers(html);
133
+ return await bridge.render(finalHtml, resolved);
134
+ }
135
+ catch (reason) {
136
+ const unsupported = unsupportedFromDiagnostic(reason);
137
+ if (unsupported) {
138
+ throw unsupported;
139
+ }
140
+ // Already-typed Vellora errors propagate unchanged; anything else (e.g. a raw fixer/resvg
141
+ // crash) is wrapped so the public contract never leaks a bare `Error`.
142
+ if (reason instanceof VelloraError) {
143
+ throw reason;
144
+ }
145
+ throw new VelloraError(reason instanceof Error ? reason.message : String(reason));
146
+ }
147
+ }
148
+ //# sourceMappingURL=orchestrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrate.js","sourceRoot":"","sources":["../src/orchestrate.ts"],"names":[],"mappings":"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"}