@shad0wz7/nestjs-pdf 3.2.0 → 5.0.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.
package/dist/index.mjs CHANGED
@@ -1,21 +1,13 @@
1
+ import { createRequire } from "node:module";
1
2
  import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
2
3
  import { Inject, Injectable, Logger, Module, Optional } from "@nestjs/common";
3
4
  import { PDFArray, PDFDict, PDFDocument, PDFName, PDFNumber, PDFString, StandardFonts, degrees, rgb } from "pdf-lib";
4
5
  import { deepmerge } from "deepmerge-ts";
5
- import { lookup } from "node:dns/promises";
6
6
  import { existsSync, promises, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
7
+ import { lookup } from "node:dns/promises";
7
8
  import { BlockList, isIP } from "node:net";
8
9
  import { fileURLToPath } from "node:url";
9
- import { TargetType, launch } from "puppeteer";
10
- import { Browser as Browser$1, BrowserPlatform, canDownload, detectBrowserPlatform, getInstalledBrowsers, install, resolveBuildId } from "@puppeteer/browsers";
11
- import ejs from "ejs";
12
- import { Eta } from "eta";
13
- import Handlebars from "handlebars";
14
- import mjml from "mjml";
15
- import mustache from "mustache";
16
- import nunjucks from "nunjucks";
17
- import pug from "pug";
18
- //#region src/exceptions/nestjs-pdf.exception.ts
10
+ // src/exceptions/nestjs-pdf.exception.ts
19
11
  let NestjsPdfErrorCode = /* @__PURE__ */ function(NestjsPdfErrorCode) {
20
12
  NestjsPdfErrorCode["ENGINE_NOT_AVAILABLE"] = "ENGINE_NOT_AVAILABLE";
21
13
  NestjsPdfErrorCode["TEMPLATE_CONFIGURATION_ERROR"] = "TEMPLATE_CONFIGURATION_ERROR";
@@ -26,6 +18,7 @@ let NestjsPdfErrorCode = /* @__PURE__ */ function(NestjsPdfErrorCode) {
26
18
  NestjsPdfErrorCode["SIGNATURE_DEPENDENCY_ERROR"] = "SIGNATURE_DEPENDENCY_ERROR";
27
19
  NestjsPdfErrorCode["SIGNATURE_PLACEMENT_ERROR"] = "SIGNATURE_PLACEMENT_ERROR";
28
20
  NestjsPdfErrorCode["WATERMARK_ERROR"] = "WATERMARK_ERROR";
21
+ NestjsPdfErrorCode["RENDERER_CONFIGURATION_ERROR"] = "RENDERER_CONFIGURATION_ERROR";
29
22
  return NestjsPdfErrorCode;
30
23
  }({});
31
24
  var NestjsPdfException = class extends Error {
@@ -34,46 +27,43 @@ var NestjsPdfException = class extends Error {
34
27
  this.name = this.constructor.name;
35
28
  }
36
29
  };
37
- //#endregion
38
- //#region src/exceptions/browser-installation.exception.ts
30
+ // src/exceptions/browser-installation.exception.ts
39
31
  var BrowserInstallationException = class extends NestjsPdfException {
40
32
  code = "BROWSER_INSTALLATION_ERROR";
41
33
  };
42
- //#endregion
43
- //#region src/exceptions/browser-unavailable.exception.ts
34
+ // src/exceptions/browser-unavailable.exception.ts
44
35
  var BrowserUnavailableException = class extends NestjsPdfException {
45
36
  code = "BROWSER_UNAVAILABLE";
46
37
  };
47
- //#endregion
48
- //#region src/exceptions/engine-not-available.exception.ts
38
+ // src/exceptions/engine-not-available.exception.ts
49
39
  var EngineNotAvailableException = class extends NestjsPdfException {
50
40
  code = "ENGINE_NOT_AVAILABLE";
51
- constructor(engineName) {
52
- super(`${engineName} service is not available. If the problem persists, open an issue in the repo.`);
41
+ constructor(engineName, packageName, options) {
42
+ super(packageName ? `${engineName} engine is not available: the '${packageName}' package is not installed. Run \`npm install ${packageName}\` to use it.` : `${engineName} service is not available. If the problem persists, open an issue in the repo.`, options);
53
43
  }
54
44
  };
55
- //#endregion
56
- //#region src/exceptions/pdf-generation.exception.ts
45
+ // src/exceptions/pdf-generation.exception.ts
57
46
  var PdfGenerationException = class extends NestjsPdfException {
58
47
  code = "PDF_GENERATION_ERROR";
59
48
  };
60
- //#endregion
61
- //#region src/exceptions/signature-dependency.exception.ts
49
+ // src/exceptions/renderer-configuration.exception.ts
50
+ /** The PDF provider is misconfigured, or asked for an option it does not support. */
51
+ var RendererConfigurationException = class extends NestjsPdfException {
52
+ code = "RENDERER_CONFIGURATION_ERROR";
53
+ };
54
+ // src/exceptions/signature-dependency.exception.ts
62
55
  var SignatureDependencyException = class extends NestjsPdfException {
63
56
  code = "SIGNATURE_DEPENDENCY_ERROR";
64
57
  };
65
- //#endregion
66
- //#region src/exceptions/signature-placement.exception.ts
58
+ // src/exceptions/signature-placement.exception.ts
67
59
  var SignaturePlacementException = class extends NestjsPdfException {
68
60
  code = "SIGNATURE_PLACEMENT_ERROR";
69
61
  };
70
- //#endregion
71
- //#region src/exceptions/template-configuration.exception.ts
62
+ // src/exceptions/template-configuration.exception.ts
72
63
  var TemplateConfigurationException = class extends NestjsPdfException {
73
64
  code = "TEMPLATE_CONFIGURATION_ERROR";
74
65
  };
75
- //#endregion
76
- //#region src/exceptions/template-render.exception.ts
66
+ // src/exceptions/template-render.exception.ts
77
67
  var TemplateRenderException = class extends NestjsPdfException {
78
68
  engine;
79
69
  code = "TEMPLATE_RENDER_ERROR";
@@ -82,13 +72,11 @@ var TemplateRenderException = class extends NestjsPdfException {
82
72
  this.engine = engine;
83
73
  }
84
74
  };
85
- //#endregion
86
- //#region src/exceptions/watermark.exception.ts
75
+ // src/exceptions/watermark.exception.ts
87
76
  var WatermarkException = class extends NestjsPdfException {
88
77
  code = "WATERMARK_ERROR";
89
78
  };
90
- //#endregion
91
- //#region src/helpers/getRelativePath.helper.ts
79
+ // src/helpers/getRelativePath.helper.ts
92
80
  /**
93
81
  * Get the relative path to a value from the module directory.
94
82
  * @param moduleDir the path of the module, typically __dirname
@@ -102,8 +90,7 @@ function getRelativePathToValue(moduleDir, relativeToModule) {
102
90
  const fullTemplatePath = resolve(moduleDir, relativeToModule);
103
91
  return relative(process.cwd(), fullTemplatePath);
104
92
  }
105
- //#endregion
106
- //#region src/helpers/signature.helper.ts
93
+ // src/helpers/signature.helper.ts
107
94
  let pdfjsPromise;
108
95
  async function loadPdfjs() {
109
96
  if (!pdfjsPromise) try {
@@ -246,8 +233,7 @@ async function addSignatureFieldUsingAnchor(pdf, fieldName = "SignatureDebtor",
246
233
  }
247
234
  return await pdfDoc.save();
248
235
  }
249
- //#endregion
250
- //#region src/helpers/watermark.helper.ts
236
+ // src/helpers/watermark.helper.ts
251
237
  const DEFAULT_OPACITY = .15;
252
238
  const DEFAULT_ROTATION = 45;
253
239
  const DEFAULT_FONT_SIZE = 60;
@@ -390,8 +376,7 @@ async function addWatermark(pdf, options) {
390
376
  throw new WatermarkException(`Failed to add watermark: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
391
377
  }
392
378
  }
393
- //#endregion
394
- //#region node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
379
+ // node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
395
380
  var Node = class {
396
381
  value;
397
382
  next;
@@ -448,8 +433,7 @@ var Queue = class {
448
433
  while (this.#head) yield this.dequeue();
449
434
  }
450
435
  };
451
- //#endregion
452
- //#region node_modules/.pnpm/p-limit@7.3.2/node_modules/p-limit/index.js
436
+ // node_modules/.pnpm/p-limit@7.3.3/node_modules/p-limit/index.js
453
437
  function pLimit(concurrency) {
454
438
  let rejectOnClear = false;
455
439
  if (typeof concurrency === "object") ({concurrency, rejectOnClear = false} = concurrency);
@@ -508,7 +492,17 @@ function pLimit(concurrency) {
508
492
  }
509
493
  },
510
494
  map: { async value(iterable, function_) {
511
- const promises = Array.from(iterable, (value, index) => generator(function_, value, index));
495
+ const promises = [];
496
+ try {
497
+ Array.from(iterable, (value, index) => {
498
+ const promise = generator(function_, value, index);
499
+ promises.push(promise);
500
+ return promise;
501
+ });
502
+ } catch (error) {
503
+ for (const promise of promises) promise.catch(() => {});
504
+ throw error;
505
+ }
512
506
  return Promise.all(promises);
513
507
  } }
514
508
  });
@@ -517,14 +511,296 @@ function pLimit(concurrency) {
517
511
  function validateConcurrency(concurrency) {
518
512
  if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
519
513
  }
520
- //#endregion
521
- //#region src/helpers/deepMergePdfparams.ts
514
+ // src/helpers/deepMergePdfparams.ts
522
515
  function mergePuppeteerParameters(defaults, overrides) {
523
516
  if (!overrides) return { ...defaults };
524
517
  return deepmerge(defaults, overrides);
525
518
  }
526
- //#endregion
527
- //#region src/helpers/request-guard.helper.ts
519
+ // src/helpers/tokens.ts
520
+ const PDF_PARAMETERS = "PDF_PARAMETERS";
521
+ // src/renderers/pdf-renderer.interface.ts
522
+ const PDF_RENDERER = "PDF_RENDERER";
523
+ // src/renderers/http.utils.ts
524
+ const MAX_ERROR_BODY_LENGTH = 500;
525
+ /** Builds a `PdfGenerationException` from a failed HTTP response, body included. */
526
+ async function httpError(provider, response) {
527
+ let body = "";
528
+ try {
529
+ body = (await response.text()).trim().slice(0, MAX_ERROR_BODY_LENGTH);
530
+ } catch {}
531
+ return new PdfGenerationException(`${provider} responded with HTTP ${response.status}${body ? `: ${body}` : ""}`);
532
+ }
533
+ /** Remote providers render on another machine: the local request guard cannot run there. */
534
+ function assertNoRequestGuard(provider, options, hint) {
535
+ if (options.security) throw new RendererConfigurationException(`The 'security' option is only supported by the puppeteer provider, not by ${provider}. ${hint}`);
536
+ }
537
+ function requireNonEmpty(value, name, provider) {
538
+ if (!value?.trim()) throw new RendererConfigurationException(`The ${provider} provider requires a non-empty '${name}'`);
539
+ return value;
540
+ }
541
+ // src/renderers/pdf-options.mapper.ts
542
+ /** Paper sizes in inches, as Puppeteer defines them. */
543
+ const PAPER_FORMATS = {
544
+ letter: {
545
+ width: 8.5,
546
+ height: 11
547
+ },
548
+ legal: {
549
+ width: 8.5,
550
+ height: 14
551
+ },
552
+ tabloid: {
553
+ width: 11,
554
+ height: 17
555
+ },
556
+ ledger: {
557
+ width: 17,
558
+ height: 11
559
+ },
560
+ a0: {
561
+ width: 33.1102,
562
+ height: 46.811
563
+ },
564
+ a1: {
565
+ width: 23.3858,
566
+ height: 33.1102
567
+ },
568
+ a2: {
569
+ width: 16.5354,
570
+ height: 23.3858
571
+ },
572
+ a3: {
573
+ width: 11.6929,
574
+ height: 16.5354
575
+ },
576
+ a4: {
577
+ width: 8.2677,
578
+ height: 11.6929
579
+ },
580
+ a5: {
581
+ width: 5.8268,
582
+ height: 8.2677
583
+ },
584
+ a6: {
585
+ width: 4.1339,
586
+ height: 5.8268
587
+ }
588
+ };
589
+ const INCHES_PER_UNIT = {
590
+ px: 1 / 96,
591
+ in: 1,
592
+ cm: 1 / 2.54,
593
+ mm: 1 / 25.4
594
+ };
595
+ /**
596
+ * Converts a Puppeteer length (a number of pixels, or a string with a
597
+ * `px`/`in`/`cm`/`mm` unit) to inches.
598
+ */
599
+ function toInches(value) {
600
+ if (typeof value === "number") return value * INCHES_PER_UNIT.px;
601
+ const match = /^\s*(-?\d*\.?\d+)\s*(px|in|cm|mm)?\s*$/i.exec(value);
602
+ if (!match) throw new RendererConfigurationException(`Invalid length '${value}': expected a number of pixels or a value in px, in, cm or mm`);
603
+ const unit = (match[2] ?? "px").toLowerCase();
604
+ return Number(match[1]) * INCHES_PER_UNIT[unit];
605
+ }
606
+ function formatInches(inches) {
607
+ return String(Math.round(inches * 1e4) / 1e4);
608
+ }
609
+ function resolvePrintLayout(pdfOptions) {
610
+ let width;
611
+ let height;
612
+ if (pdfOptions.width !== void 0 || pdfOptions.height !== void 0) {
613
+ const fallback = PAPER_FORMATS.letter;
614
+ width = pdfOptions.width === void 0 ? fallback.width : toInches(pdfOptions.width);
615
+ height = pdfOptions.height === void 0 ? fallback.height : toInches(pdfOptions.height);
616
+ } else {
617
+ const formatName = (pdfOptions.format ?? "letter").toLowerCase();
618
+ const format = PAPER_FORMATS[formatName];
619
+ if (!format) throw new RendererConfigurationException(`Unknown paper format '${pdfOptions.format}'`);
620
+ ({width, height} = format);
621
+ }
622
+ const margin = {};
623
+ for (const side of [
624
+ "top",
625
+ "right",
626
+ "bottom",
627
+ "left"
628
+ ]) {
629
+ const value = pdfOptions.margin?.[side];
630
+ if (value !== void 0) margin[side] = toInches(value);
631
+ }
632
+ return {
633
+ width,
634
+ height,
635
+ margin
636
+ };
637
+ }
638
+ function toHtmlDocument(template) {
639
+ return /<html[\s>]/i.test(template) ? template : `<!DOCTYPE html><html><head></head><body>${template}</body></html>`;
640
+ }
641
+ function toGotenbergForm(pdfOptions) {
642
+ const { width, height, margin } = resolvePrintLayout(pdfOptions);
643
+ const fields = {
644
+ paperWidth: formatInches(width),
645
+ paperHeight: formatInches(height),
646
+ marginTop: formatInches(margin.top ?? 0),
647
+ marginRight: formatInches(margin.right ?? 0),
648
+ marginBottom: formatInches(margin.bottom ?? 0),
649
+ marginLeft: formatInches(margin.left ?? 0),
650
+ emulatedMediaType: "screen",
651
+ skipNetworkIdleEvent: "false"
652
+ };
653
+ if (pdfOptions.landscape !== void 0) fields.landscape = String(pdfOptions.landscape);
654
+ if (pdfOptions.printBackground !== void 0) fields.printBackground = String(pdfOptions.printBackground);
655
+ if (pdfOptions.omitBackground !== void 0) fields.omitBackground = String(pdfOptions.omitBackground);
656
+ if (pdfOptions.scale !== void 0) fields.scale = String(pdfOptions.scale);
657
+ if (pdfOptions.pageRanges) fields.nativePageRanges = pdfOptions.pageRanges;
658
+ if (pdfOptions.preferCSSPageSize !== void 0) fields.preferCssPageSize = String(pdfOptions.preferCSSPageSize);
659
+ if (pdfOptions.tagged !== void 0) fields.generateTaggedPdf = String(pdfOptions.tagged);
660
+ if (pdfOptions.outline !== void 0) fields.generateDocumentOutline = String(pdfOptions.outline);
661
+ const files = {};
662
+ if (pdfOptions.displayHeaderFooter) {
663
+ if (pdfOptions.headerTemplate) files["header.html"] = toHtmlDocument(pdfOptions.headerTemplate);
664
+ if (pdfOptions.footerTemplate) files["footer.html"] = toHtmlDocument(pdfOptions.footerTemplate);
665
+ }
666
+ return {
667
+ fields,
668
+ files
669
+ };
670
+ }
671
+ function toApi2PdfOptions(pdfOptions) {
672
+ const { width, height, margin } = resolvePrintLayout(pdfOptions);
673
+ const options = {
674
+ width: `${formatInches(width)}in`,
675
+ height: `${formatInches(height)}in`,
676
+ marginTop: `${formatInches(margin.top ?? 0)}in`,
677
+ marginRight: `${formatInches(margin.right ?? 0)}in`,
678
+ marginBottom: `${formatInches(margin.bottom ?? 0)}in`,
679
+ marginLeft: `${formatInches(margin.left ?? 0)}in`,
680
+ usePrintCss: false
681
+ };
682
+ for (const key of [
683
+ "landscape",
684
+ "printBackground",
685
+ "omitBackground",
686
+ "scale",
687
+ "pageRanges",
688
+ "displayHeaderFooter",
689
+ "headerTemplate",
690
+ "footerTemplate"
691
+ ]) if (pdfOptions[key] !== void 0) options[key] = pdfOptions[key];
692
+ return options;
693
+ }
694
+ // src/renderers/api2pdf.renderer.ts
695
+ const PROVIDER$1 = "Api2Pdf";
696
+ const DEFAULT_BASE_URL = "https://v2.api2pdf.com";
697
+ const ROUTE$1 = "/chrome/pdf/html";
698
+ function isApi2Pdf(data) {
699
+ return typeof data === "object" && data !== null && ("Success" in data || "Error" in data || "FileUrl" in data);
700
+ }
701
+ var Api2PdfRenderer = class {
702
+ #endpoint;
703
+ #headers;
704
+ constructor(provider) {
705
+ const apiKey = requireNonEmpty(provider.apiKey, "apiKey", PROVIDER$1);
706
+ const baseUrl = requireNonEmpty(provider.baseUrl ?? DEFAULT_BASE_URL, "baseUrl", PROVIDER$1).replace(/\/+$/, "");
707
+ this.#endpoint = `${baseUrl}${ROUTE$1}?outputBinary=true`;
708
+ this.#headers = {
709
+ ...provider.headers,
710
+ Authorization: apiKey,
711
+ "Content-Type": "application/json",
712
+ Accept: "application/pdf, application/json"
713
+ };
714
+ }
715
+ async render(html, options, signal) {
716
+ assertNoRequestGuard(PROVIDER$1, options, "Only render trusted HTML with a remote provider.");
717
+ const body = {
718
+ html,
719
+ inline: false,
720
+ options: {
721
+ ...toApi2PdfOptions(options.pdfOptions ?? { format: "A4" }),
722
+ ...options.api2pdfOptions?.options
723
+ }
724
+ };
725
+ if (options.api2pdfOptions?.fileName) body.fileName = options.api2pdfOptions.fileName;
726
+ const response = await fetch(this.#endpoint, {
727
+ method: "POST",
728
+ headers: this.#headers,
729
+ body: JSON.stringify(body),
730
+ signal
731
+ });
732
+ if (!response.ok) throw await httpError(PROVIDER$1, response);
733
+ if (!response.headers.get("content-type")?.includes("application/json")) return new Uint8Array(await response.arrayBuffer());
734
+ const result = await response.json();
735
+ if (!isApi2Pdf(result)) throw new PdfGenerationException(`${PROVIDER$1} returned an unexpected response: ${JSON.stringify(result)}`);
736
+ if (result.Success === false || !result.FileUrl) throw new PdfGenerationException(`${PROVIDER$1} could not generate the PDF: ${result.Error ?? "no file returned"}`);
737
+ const file = await fetch(result.FileUrl, { signal });
738
+ if (!file.ok) throw await httpError(PROVIDER$1, file);
739
+ return new Uint8Array(await file.arrayBuffer());
740
+ }
741
+ };
742
+ // src/renderers/gotenberg.renderer.ts
743
+ const PROVIDER = "Gotenberg";
744
+ const ROUTE = "/forms/chromium/convert/html";
745
+ var GotenbergRenderer = class {
746
+ #endpoint;
747
+ #headers;
748
+ constructor(provider) {
749
+ const url = requireNonEmpty(provider.url, "url", PROVIDER);
750
+ this.#endpoint = `${url.replace(/\/+$/, "")}${ROUTE}`;
751
+ this.#headers = { ...provider.headers };
752
+ if (provider.basicAuth) {
753
+ const { username, password } = provider.basicAuth;
754
+ this.#headers.Authorization = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
755
+ }
756
+ }
757
+ async render(html, options, signal) {
758
+ assertNoRequestGuard(PROVIDER, options, "Restrict outbound requests on the Gotenberg server instead, with '--chromium-allow-list' / '--chromium-deny-list'.");
759
+ const { fields, files } = toGotenbergForm(options.pdfOptions ?? { format: "A4" });
760
+ const form = new FormData();
761
+ form.append("files", new Blob([html], { type: "text/html" }), "index.html");
762
+ for (const [name, content] of Object.entries(files)) form.append("files", new Blob([content], { type: "text/html" }), name);
763
+ for (const asset of options.gotenbergOptions?.assets ?? []) form.append("files", new Blob([asset.content], { type: asset.contentType ?? "application/octet-stream" }), asset.filename);
764
+ const allFields = {
765
+ ...fields,
766
+ ...options.gotenbergOptions?.formFields
767
+ };
768
+ for (const [name, value] of Object.entries(allFields)) form.append(name, value);
769
+ const response = await fetch(this.#endpoint, {
770
+ method: "POST",
771
+ headers: this.#headers,
772
+ body: form,
773
+ signal
774
+ });
775
+ if (!response.ok) throw await httpError(PROVIDER, response);
776
+ return new Uint8Array(await response.arrayBuffer());
777
+ }
778
+ };
779
+ // src/puppeteer/libs/loadPuppeteer.utils.ts
780
+ function isMissingPackage(error, packageName) {
781
+ const { code, message } = error;
782
+ return (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") && typeof message === "string" && message.includes(`'${packageName}'`);
783
+ }
784
+ /** Imports an optional peer dependency, turning "not installed" into a clear exception. */
785
+ async function loadOptional(packageName, importer) {
786
+ try {
787
+ return await importer();
788
+ } catch (error) {
789
+ if (isMissingPackage(error, packageName)) throw new EngineNotAvailableException("Puppeteer", packageName, { cause: error });
790
+ throw error;
791
+ }
792
+ }
793
+ /**
794
+ * Puppeteer is an optional peer dependency, only needed by the `puppeteer`
795
+ * provider: it is imported on first use, never when the library loads.
796
+ */
797
+ function loadPuppeteer() {
798
+ return loadOptional("puppeteer", () => import("puppeteer"));
799
+ }
800
+ function loadPuppeteerBrowsers() {
801
+ return loadOptional("@puppeteer/browsers", () => import("@puppeteer/browsers"));
802
+ }
803
+ // src/helpers/request-guard.helper.ts
528
804
  const blockedRanges = new BlockList();
529
805
  for (const [address, prefix] of [
530
806
  ["0.0.0.0", 8],
@@ -652,6 +928,7 @@ function describe(rawUrl) {
652
928
  * and closes the pop-ups a script could open to escape the filter.
653
929
  */
654
930
  async function installRequestGuard(context, page, options) {
931
+ const { TargetType } = await loadPuppeteer();
655
932
  context.on("targetcreated", (target) => {
656
933
  if (target.type() === TargetType.PAGE && target.opener()) target.page().then((popup) => popup?.close()).catch(() => void 0);
657
934
  });
@@ -671,31 +948,162 @@ async function installRequestGuard(context, page, options) {
671
948
  })().catch(() => void 0);
672
949
  });
673
950
  }
674
- //#endregion
675
- //#region src/helpers/tokens.ts
676
- const PDF_PARAMETERS = "PDF_PARAMETERS";
677
- //#endregion
678
- //#region \0@oxc-project+runtime@0.149.0/helpers/esm/decorateMetadata.js
951
+ // src/renderers/puppeteer.renderer.ts
952
+ const DEFAULT_ARGS = [
953
+ "--autoplay-policy=user-gesture-required",
954
+ "--disable-background-networking",
955
+ "--disable-background-timer-throttling",
956
+ "--disable-backgrounding-occluded-windows",
957
+ "--disable-breakpad",
958
+ "--disable-client-side-phishing-detection",
959
+ "--disable-component-update",
960
+ "--disable-default-apps",
961
+ "--disable-dev-shm-usage",
962
+ "--disable-domain-reliability",
963
+ "--disable-extensions",
964
+ "--disable-features=AudioServiceOutOfProcess",
965
+ "--disable-hang-monitor",
966
+ "--disable-ipc-flooding-protection",
967
+ "--disable-notifications",
968
+ "--disable-offer-store-unmasked-wallet-cards",
969
+ "--disable-print-preview",
970
+ "--disable-prompt-on-repost",
971
+ "--disable-renderer-backgrounding",
972
+ "--disable-setuid-sandbox",
973
+ "--disable-speech-api",
974
+ "--disable-sync",
975
+ "--hide-scrollbars",
976
+ "--ignore-gpu-blacklist",
977
+ "--metrics-recording-only",
978
+ "--mute-audio",
979
+ "--no-default-browser-check",
980
+ "--no-first-run",
981
+ "--no-pings",
982
+ "--password-store=basic",
983
+ "--use-gl=swiftshader",
984
+ "--use-mock-keychain",
985
+ "--disable-accelerated-2d-canvas",
986
+ "--no-zygote",
987
+ "--disable-gpu"
988
+ ];
989
+ const PDF_OPTION_KEYS = {
990
+ scale: true,
991
+ displayHeaderFooter: true,
992
+ headerTemplate: true,
993
+ footerTemplate: true,
994
+ printBackground: true,
995
+ landscape: true,
996
+ pageRanges: true,
997
+ format: true,
998
+ width: true,
999
+ height: true,
1000
+ preferCSSPageSize: true,
1001
+ margin: true,
1002
+ path: true,
1003
+ omitBackground: true,
1004
+ tagged: true,
1005
+ outline: true,
1006
+ timeout: true,
1007
+ waitForFonts: true
1008
+ };
1009
+ function toPuppeteerPdfOptions(options) {
1010
+ if (!options) return { format: "A4" };
1011
+ const invalidKeys = Object.keys(options).filter((key) => !Object.hasOwn(PDF_OPTION_KEYS, key));
1012
+ if (invalidKeys.length > 0) throw new RendererConfigurationException(`Invalid PDF options: ${invalidKeys.join(", ")}. Please check the documentation for valid options.`);
1013
+ return options;
1014
+ }
1015
+ var PuppeteerRenderer = class {
1016
+ #browserService;
1017
+ constructor(browserService) {
1018
+ this.#browserService = browserService;
1019
+ }
1020
+ async render(html, options, signal) {
1021
+ if (options.chromiumRevision !== void 0) Logger.warn("Using `chromiumRevision` is no longer supported since the puppeteer update.");
1022
+ const pdfOptions = toPuppeteerPdfOptions(options.pdfOptions);
1023
+ const headless = options.headless ?? true;
1024
+ const executablePath = options.executablePath || void 0;
1025
+ const args = options.extraPuppeteerArgs || DEFAULT_ARGS;
1026
+ let context = null;
1027
+ let closing;
1028
+ const onAbort = () => {
1029
+ if (!context) return;
1030
+ closing = context.close();
1031
+ closing.catch(() => void 0);
1032
+ };
1033
+ signal?.addEventListener("abort", onAbort, { once: true });
1034
+ try {
1035
+ context = await this.#browserService.createContext(args, headless, executablePath);
1036
+ signal?.throwIfAborted();
1037
+ const page = await context.newPage();
1038
+ if (options.security) await installRequestGuard(context, page, options.security);
1039
+ await page.setContent(html, { waitUntil: "domcontentloaded" });
1040
+ await page.emulateMediaType("screen");
1041
+ await page.waitForNetworkIdle({
1042
+ idleTime: 500,
1043
+ timeout: 3e4
1044
+ });
1045
+ /* v8 ignore next */
1046
+ await page.evaluate(() => document.fonts.ready);
1047
+ return await page.pdf(pdfOptions);
1048
+ } finally {
1049
+ signal?.removeEventListener("abort", onAbort);
1050
+ if (context) try {
1051
+ if (closing) await closing;
1052
+ else await context.close();
1053
+ } catch (error) {
1054
+ Logger.error(error);
1055
+ }
1056
+ }
1057
+ }
1058
+ };
1059
+ // src/renderers/create-renderer.ts
1060
+ /** True when the PDFs are printed by the local Chromium (the default). */
1061
+ function usesPuppeteer(params) {
1062
+ return (params.provider?.type ?? "puppeteer") === "puppeteer";
1063
+ }
1064
+ function createPdfRenderer(params, browserService) {
1065
+ const provider = params.provider ?? { type: "puppeteer" };
1066
+ switch (provider.type) {
1067
+ case "puppeteer": return new PuppeteerRenderer(browserService);
1068
+ case "gotenberg": return new GotenbergRenderer(provider);
1069
+ case "api2pdf": return new Api2PdfRenderer(provider);
1070
+ default: throw new RendererConfigurationException(`Unknown PDF provider '${String(provider.type)}': expected 'puppeteer', 'gotenberg' or 'api2pdf'`);
1071
+ }
1072
+ }
1073
+ // src/puppeteer/libs/enum.utils.ts
1074
+ /**
1075
+ * Converts a string value to an enum member, throwing a RendererConfigurationException if the value is not a valid member of the enum.
1076
+ * @param enumObject
1077
+ * @param value
1078
+ * @param label
1079
+ * @throws RendererConfigurationException
1080
+ */
1081
+ function toEnumMember(enumObject, value, label) {
1082
+ const members = Object.values(enumObject);
1083
+ const isMember = (candidate) => candidate === value;
1084
+ const member = members.find(isMember);
1085
+ if (member === void 0) throw new RendererConfigurationException(`Unsupported ${label} '${value}': expected one of ${members.map((m) => `'${m}'`).join(", ")}`);
1086
+ return member;
1087
+ }
1088
+ // \0@oxc-project+runtime@0.156.0/helpers/esm/decorateMetadata.js
679
1089
  function __decorateMetadata(k, v) {
680
1090
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
681
1091
  }
682
- //#endregion
683
- //#region \0@oxc-project+runtime@0.149.0/helpers/esm/decorateParam.js
1092
+ // \0@oxc-project+runtime@0.156.0/helpers/esm/decorateParam.js
684
1093
  function __decorateParam(paramIndex, decorator) {
685
1094
  return function(target, key) {
686
1095
  decorator(target, key, paramIndex);
687
1096
  };
688
1097
  }
689
- //#endregion
690
- //#region \0@oxc-project+runtime@0.149.0/helpers/esm/decorate.js
1098
+ // \0@oxc-project+runtime@0.156.0/helpers/esm/decorate.js
691
1099
  function __decorate(decorators, target, key, desc) {
692
1100
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
693
1101
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
694
1102
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
695
1103
  return c > 3 && r && Object.defineProperty(target, key, r), r;
696
1104
  }
697
- //#endregion
698
- //#region src/puppeteer/browser/browser.service.ts
1105
+ // src/puppeteer/browser/browser.service.ts
1106
+ const DEFAULT_BROWSER = "chromium";
699
1107
  async function sleep(ms) {
700
1108
  await new Promise((r) => setTimeout(r, ms));
701
1109
  }
@@ -747,7 +1155,7 @@ let BrowserService = class BrowserService {
747
1155
  } catch (error) {
748
1156
  Logger.warn(`Failed to close browser on module destroy: ${error instanceof Error ? error.message : String(error)}`);
749
1157
  }
750
- if (this.#options?.cleanupBrowserCacheOnExit !== false) await this.#cleanupCacheBestEffort();
1158
+ if (this.#options !== void 0 && usesPuppeteer(this.#options) && this.#options.cleanupBrowserCacheOnExit !== false) await this.#cleanupCacheBestEffort();
751
1159
  }
752
1160
  /** Wait until no PDF job is in flight, or until the timeout elapses. */
753
1161
  async #waitForIdle(timeoutMs) {
@@ -829,7 +1237,8 @@ let BrowserService = class BrowserService {
829
1237
  }
830
1238
  }
831
1239
  async #doInstall(lock) {
832
- const browser = this.#browser;
1240
+ const { Browser: BrowserEnum, BrowserPlatform, canDownload, detectBrowserPlatform, install, resolveBuildId } = await loadPuppeteerBrowsers();
1241
+ const browser = toEnumMember(BrowserEnum, this.#browser, "browser");
833
1242
  const versionTag = this.#browserTag;
834
1243
  const browserPlatform = detectBrowserPlatform() ?? BrowserPlatform.LINUX;
835
1244
  let buildId;
@@ -861,10 +1270,7 @@ let BrowserService = class BrowserService {
861
1270
  return null;
862
1271
  }
863
1272
  #loadBrowser() {
864
- let browser;
865
- if (this.#options?.browser === void 0) browser = Browser$1.CHROMIUM;
866
- else browser = this.#options.browser;
867
- this.#browser = browser;
1273
+ this.#browser = this.#options?.browser ?? DEFAULT_BROWSER;
868
1274
  }
869
1275
  getBrowser() {
870
1276
  return this.#browser;
@@ -872,7 +1278,7 @@ let BrowserService = class BrowserService {
872
1278
  #loadBrowserTag() {
873
1279
  let versionTag;
874
1280
  if (this.#options?.browserTag === void 0) {
875
- if (this.#browser === Browser$1.CHROMIUM) versionTag = "latest";
1281
+ if (this.#browser === DEFAULT_BROWSER) versionTag = "latest";
876
1282
  else versionTag = "stable";
877
1283
  } else versionTag = this.#options.browserTag;
878
1284
  this.#browserTag = versionTag;
@@ -899,12 +1305,15 @@ let BrowserService = class BrowserService {
899
1305
  this.#useLockedBrowser = useLockedBrowser;
900
1306
  }
901
1307
  async hasBrowserInstalled(browser, buildId) {
1308
+ const { Browser: BrowserEnum, getInstalledBrowsers } = await loadPuppeteerBrowsers();
1309
+ const expected = toEnumMember(BrowserEnum, browser, "browser");
902
1310
  return (await getInstalledBrowsers({ cacheDir: this.#cacheDir })).find((insBrowser) => {
903
- return insBrowser.browser === browser && insBrowser.buildId === buildId;
1311
+ return insBrowser.browser === expected && insBrowser.buildId === buildId;
904
1312
  }) !== void 0;
905
1313
  }
906
1314
  async getExecutablePath() {
907
- const browser = this.#browser;
1315
+ const { Browser: BrowserEnum, BrowserPlatform, detectBrowserPlatform, getInstalledBrowsers, resolveBuildId } = await loadPuppeteerBrowsers();
1316
+ const browser = toEnumMember(BrowserEnum, this.#browser, "browser");
908
1317
  const versionTag = this.#browserTag;
909
1318
  const browserPlatform = detectBrowserPlatform() ?? BrowserPlatform.LINUX;
910
1319
  let buildId = null;
@@ -925,6 +1334,7 @@ let BrowserService = class BrowserService {
925
1334
  }
926
1335
  async #launchBrowser(args, headless, executablePathBin) {
927
1336
  const executablePath = executablePathBin ?? await this.getExecutablePath();
1337
+ const { launch } = await loadPuppeteer();
928
1338
  const browser = await launch({
929
1339
  executablePath,
930
1340
  headless,
@@ -961,12 +1371,33 @@ BrowserService = __decorate([
961
1371
  __decorateParam(0, Inject("PDF_PARAMETERS")),
962
1372
  __decorateMetadata("design:paramtypes", [Object])
963
1373
  ], BrowserService);
964
- //#endregion
965
- //#region src/puppeteer/engines/ejs/ejs.service.ts
1374
+ // src/puppeteer/libs/loadEngine.utils.ts
1375
+ const requireEngine = createRequire(import.meta.url);
1376
+ /**
1377
+ * Load an optional template engine on first use, so the library can be imported
1378
+ * without every engine installed. `require` (not `import()`) is used on purpose:
1379
+ * most engines expose a synchronous render API.
1380
+ */
1381
+ function loadEngine(packageName, engineName) {
1382
+ let mod;
1383
+ try {
1384
+ mod = requireEngine(packageName);
1385
+ } catch (error) {
1386
+ if (error.code === "MODULE_NOT_FOUND") throw new EngineNotAvailableException(engineName, packageName, { cause: error });
1387
+ throw error;
1388
+ }
1389
+ return mod.__esModule && "default" in mod ? mod.default : mod;
1390
+ }
1391
+ // src/puppeteer/engines/ejs/ejs.service.ts
966
1392
  let EjsService = class EjsService {
1393
+ #ejs;
1394
+ get #engine() {
1395
+ return this.#ejs ??= loadEngine("ejs", "EJS");
1396
+ }
967
1397
  async render(template, data = {}, options) {
1398
+ const engine = this.#engine;
968
1399
  try {
969
- return await ejs.render(template, data, {
1400
+ return await engine.render(template, data, {
970
1401
  async: true,
971
1402
  ...options
972
1403
  });
@@ -975,8 +1406,9 @@ let EjsService = class EjsService {
975
1406
  }
976
1407
  }
977
1408
  async renderFile(filePath, data = {}, options) {
1409
+ const engine = this.#engine;
978
1410
  try {
979
- return await ejs.renderFile(filePath, data, {
1411
+ return await engine.renderFile(filePath, data, {
980
1412
  async: true,
981
1413
  ...options
982
1414
  });
@@ -986,18 +1418,14 @@ let EjsService = class EjsService {
986
1418
  }
987
1419
  };
988
1420
  EjsService = __decorate([Injectable()], EjsService);
989
- //#endregion
990
- //#region src/puppeteer/engines/eta/eta.service.ts
1421
+ // src/puppeteer/engines/eta/eta.service.ts
991
1422
  let EtaService = class EtaService {
1423
+ #etaModule;
992
1424
  #eta;
993
1425
  #templateCache = /* @__PURE__ */ new Map();
994
1426
  #maxCacheSize = 100;
995
- constructor() {
996
- this.#eta = new Eta({
997
- cache: true,
998
- autoEscape: true,
999
- useWith: true
1000
- });
1427
+ get #engine() {
1428
+ return this.#etaModule ??= loadEngine("eta", "Eta");
1001
1429
  }
1002
1430
  /**
1003
1431
  * Render an Eta template from a string
@@ -1007,6 +1435,7 @@ let EtaService = class EtaService {
1007
1435
  * @returns The rendered HTML string
1008
1436
  */
1009
1437
  render(template, data = {}, options) {
1438
+ const { Eta } = this.#engine;
1010
1439
  try {
1011
1440
  return (options && Object.keys(options).length > 0 ? new Eta({
1012
1441
  cache: true,
@@ -1014,7 +1443,11 @@ let EtaService = class EtaService {
1014
1443
  useWith: true,
1015
1444
  ...options,
1016
1445
  varName: options.varName ?? "it"
1017
- }) : this.#eta).renderString(template, data);
1446
+ }) : this.#eta ??= new Eta({
1447
+ cache: true,
1448
+ autoEscape: true,
1449
+ useWith: true
1450
+ })).renderString(template, data);
1018
1451
  } catch (error) {
1019
1452
  throw new TemplateRenderException("Eta", `Eta rendering failed: ${String(error)}`, { cause: error });
1020
1453
  }
@@ -1037,6 +1470,7 @@ let EtaService = class EtaService {
1037
1470
  }
1038
1471
  return this.render(template, data, options);
1039
1472
  } catch (error) {
1473
+ if (error instanceof EngineNotAvailableException) throw error;
1040
1474
  throw new TemplateRenderException("Eta", `Eta file rendering failed for ${filePath}: ${String(error)}`, { cause: error });
1041
1475
  }
1042
1476
  }
@@ -1054,12 +1488,15 @@ let EtaService = class EtaService {
1054
1488
  return this.#templateCache.size;
1055
1489
  }
1056
1490
  };
1057
- EtaService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [])], EtaService);
1058
- //#endregion
1059
- //#region src/puppeteer/engines/handlebars/handlebars.service.ts
1491
+ EtaService = __decorate([Injectable()], EtaService);
1492
+ // src/puppeteer/engines/handlebars/handlebars.service.ts
1060
1493
  let HandlebarsService = class HandlebarsService {
1494
+ #handlebars;
1061
1495
  #cachedOptions;
1062
1496
  #cachedEnv;
1497
+ get #engine() {
1498
+ return this.#handlebars ??= loadEngine("handlebars", "Handlebars");
1499
+ }
1063
1500
  render(template, parameters = {}, options = {}) {
1064
1501
  try {
1065
1502
  return this.#getEnv(options).compile(template, options.compileOptions)(parameters, options.templateOptions);
@@ -1088,7 +1525,7 @@ let HandlebarsService = class HandlebarsService {
1088
1525
  */
1089
1526
  #getEnv(options) {
1090
1527
  if (this.#cachedEnv && this.#cachedOptions === options) return this.#cachedEnv;
1091
- const env = Handlebars.create();
1528
+ const env = this.#engine.create();
1092
1529
  for (const helper of options.helpers ?? []) env.registerHelper(helper.name, helper.fn);
1093
1530
  this.#registerPartials(env, options.partialDirectory);
1094
1531
  this.#cachedOptions = options;
@@ -1107,12 +1544,77 @@ let HandlebarsService = class HandlebarsService {
1107
1544
  }
1108
1545
  };
1109
1546
  HandlebarsService = __decorate([Injectable()], HandlebarsService);
1110
- //#endregion
1111
- //#region src/puppeteer/engines/mjml/mjml.service.ts
1547
+ // src/puppeteer/engines/liquid/liquid.service.ts
1548
+ let LiquidService = class LiquidService {
1549
+ #liquidModule;
1550
+ #liquid;
1551
+ #templateCache = /* @__PURE__ */ new Map();
1552
+ #maxCacheSize = 100;
1553
+ get #engine() {
1554
+ return this.#liquidModule ??= loadEngine("liquidjs", "Liquid");
1555
+ }
1556
+ /**
1557
+ * Render a Liquid template from a string
1558
+ * @param template The template string to render
1559
+ * @param data The data object to use for rendering
1560
+ * @param options Optional Liquid engine options
1561
+ * @returns The rendered HTML string
1562
+ */
1563
+ render(template, data = {}, options) {
1564
+ const { Liquid } = this.#engine;
1565
+ try {
1566
+ return (options && Object.keys(options).length > 0 ? new Liquid(options) : this.#liquid ??= new Liquid()).parseAndRenderSync(template, data);
1567
+ } catch (error) {
1568
+ throw new TemplateRenderException("Liquid", `Liquid rendering failed: ${String(error)}`, { cause: error });
1569
+ }
1570
+ }
1571
+ /**
1572
+ * Render a Liquid template from a file
1573
+ * @param filePath The path to the template file
1574
+ * @param data The data object to use for rendering
1575
+ * @param options Optional Liquid engine options
1576
+ * @returns The rendered HTML string
1577
+ */
1578
+ renderFile(filePath, data = {}, options) {
1579
+ try {
1580
+ const resolvedPath = resolve(filePath);
1581
+ const useCache = options?.cache !== false;
1582
+ let template = useCache ? this.#templateCache.get(resolvedPath) : void 0;
1583
+ if (!template) {
1584
+ template = readFileSync(resolvedPath, "utf-8");
1585
+ if (useCache && this.#templateCache.size < this.#maxCacheSize) this.#templateCache.set(resolvedPath, template);
1586
+ }
1587
+ return this.render(template, data, options);
1588
+ } catch (error) {
1589
+ if (error instanceof EngineNotAvailableException) throw error;
1590
+ throw new TemplateRenderException("Liquid", `Liquid file rendering failed for ${filePath}: ${String(error)}`, { cause: error });
1591
+ }
1592
+ }
1593
+ /**
1594
+ * Clear the template cache
1595
+ */
1596
+ clearCache() {
1597
+ this.#templateCache.clear();
1598
+ }
1599
+ /**
1600
+ * Get the current cache size
1601
+ * @returns The number of cached templates
1602
+ */
1603
+ getCacheSize() {
1604
+ return this.#templateCache.size;
1605
+ }
1606
+ };
1607
+ LiquidService = __decorate([Injectable()], LiquidService);
1608
+ // src/puppeteer/engines/mjml/mjml.service.ts
1112
1609
  let MjmlService = class MjmlService {
1610
+ #mjml;
1611
+ get #engine() {
1612
+ return this.#mjml ??= loadEngine("mjml", "MJML");
1613
+ }
1113
1614
  async render(template, options) {
1615
+ const engine = this.#engine;
1114
1616
  try {
1115
- const { html } = await mjml(template, options);
1617
+ const { html } = await engine(template, options);
1116
1618
  return html;
1117
1619
  } catch (error) {
1118
1620
  throw new TemplateRenderException("MJML", `MJML rendering failed: ${String(error)}`, { cause: error });
@@ -1129,11 +1631,14 @@ let MjmlService = class MjmlService {
1129
1631
  }
1130
1632
  };
1131
1633
  MjmlService = __decorate([Injectable()], MjmlService);
1132
- //#endregion
1133
- //#region src/puppeteer/engines/mustache/mustache.service.ts
1634
+ // src/puppeteer/engines/mustache/mustache.service.ts
1134
1635
  let MustacheService = class MustacheService {
1636
+ #mustache;
1135
1637
  #templateCache = /* @__PURE__ */ new Map();
1136
1638
  #maxCacheSize = 100;
1639
+ get #engine() {
1640
+ return this.#mustache ??= loadEngine("mustache", "Mustache");
1641
+ }
1137
1642
  /**
1138
1643
  * Render a Mustache template from a string
1139
1644
  * @param template The template string to render
@@ -1142,9 +1647,10 @@ let MustacheService = class MustacheService {
1142
1647
  * @returns The rendered HTML string
1143
1648
  */
1144
1649
  render(template, data = {}, options) {
1650
+ const engine = this.#engine;
1145
1651
  try {
1146
1652
  const tags = options?.tags ?? ["{{", "}}"];
1147
- return mustache.render(template, data, void 0, {
1653
+ return engine.render(template, data, void 0, {
1148
1654
  tags,
1149
1655
  escape: options?.escape
1150
1656
  });
@@ -1169,6 +1675,7 @@ let MustacheService = class MustacheService {
1169
1675
  }
1170
1676
  return this.render(template, data, options);
1171
1677
  } catch (error) {
1678
+ if (error instanceof EngineNotAvailableException) throw error;
1172
1679
  throw new TemplateRenderException("Mustache", `Mustache file rendering failed for ${filePath}: ${String(error)}`, { cause: error });
1173
1680
  }
1174
1681
  }
@@ -1187,17 +1694,22 @@ let MustacheService = class MustacheService {
1187
1694
  }
1188
1695
  };
1189
1696
  MustacheService = __decorate([Injectable()], MustacheService);
1190
- //#endregion
1191
- //#region src/puppeteer/engines/nunjucks/nunjucks.service.ts
1697
+ // src/puppeteer/engines/nunjucks/nunjucks.service.ts
1192
1698
  let NunjucksService = class NunjucksService {
1193
- constructor() {
1194
- nunjucks.configure({
1195
- noCache: true,
1196
- trimBlocks: true,
1197
- lstripBlocks: true
1198
- });
1699
+ #nunjucks;
1700
+ get #engine() {
1701
+ if (!this.#nunjucks) {
1702
+ this.#nunjucks = loadEngine("nunjucks", "Nunjucks");
1703
+ this.#nunjucks.configure({
1704
+ noCache: true,
1705
+ trimBlocks: true,
1706
+ lstripBlocks: true
1707
+ });
1708
+ }
1709
+ return this.#nunjucks;
1199
1710
  }
1200
1711
  render(template, data = {}, options) {
1712
+ const nunjucks = this.#engine;
1201
1713
  try {
1202
1714
  if (options) {
1203
1715
  const configOptions = {
@@ -1215,6 +1727,7 @@ let NunjucksService = class NunjucksService {
1215
1727
  }
1216
1728
  }
1217
1729
  renderFile(filePath, data = {}, options) {
1730
+ const nunjucks = this.#engine;
1218
1731
  try {
1219
1732
  if (options) {
1220
1733
  const configOptions = {
@@ -1232,13 +1745,17 @@ let NunjucksService = class NunjucksService {
1232
1745
  }
1233
1746
  }
1234
1747
  };
1235
- NunjucksService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [])], NunjucksService);
1236
- //#endregion
1237
- //#region src/puppeteer/engines/pug/pug.service.ts
1748
+ NunjucksService = __decorate([Injectable()], NunjucksService);
1749
+ // src/puppeteer/engines/pug/pug.service.ts
1238
1750
  let PugService = class PugService {
1751
+ #pug;
1752
+ get #engine() {
1753
+ return this.#pug ??= loadEngine("pug", "Pug");
1754
+ }
1239
1755
  render(template, data = {}, options) {
1756
+ const engine = this.#engine;
1240
1757
  try {
1241
- return pug.compile(template, { ...options })(data);
1758
+ return engine.compile(template, { ...options })(data);
1242
1759
  } catch (error) {
1243
1760
  throw new TemplateRenderException("Pug", `Pug rendering failed: ${String(error)}`, { cause: error });
1244
1761
  }
@@ -1248,21 +1765,21 @@ let PugService = class PugService {
1248
1765
  const template = readFileSync(filePath, "utf-8");
1249
1766
  return this.render(template, data, options);
1250
1767
  } catch (error) {
1768
+ if (error instanceof EngineNotAvailableException) throw error;
1251
1769
  throw new TemplateRenderException("Pug", `Pug file rendering failed: ${String(error)}`, { cause: error });
1252
1770
  }
1253
1771
  }
1254
1772
  };
1255
1773
  PugService = __decorate([Injectable()], PugService);
1256
- //#endregion
1257
- //#region src/puppeteer/libs/requireService.utils.ts
1774
+ // src/puppeteer/libs/requireService.utils.ts
1258
1775
  function requireService(service, name) {
1259
1776
  if (!service) throw new EngineNotAvailableException(name);
1260
1777
  return service;
1261
1778
  }
1262
- //#endregion
1263
- //#region src/puppeteer/puppeteer.service.ts
1779
+ // src/puppeteer/puppeteer.service.ts
1264
1780
  let PuppeteerService = class PuppeteerService {
1265
- #browserService;
1781
+ #renderer;
1782
+ #jobs;
1266
1783
  #options;
1267
1784
  #hbsService;
1268
1785
  #mjmlService;
@@ -1271,8 +1788,10 @@ let PuppeteerService = class PuppeteerService {
1271
1788
  #nunjucksService;
1272
1789
  #etaService;
1273
1790
  #mustacheService;
1274
- constructor(browserService, options, hbsService, mjmlService, pugService, ejsService, nunjucksService, etaService, mustacheService) {
1275
- this.#browserService = browserService;
1791
+ #liquidService;
1792
+ constructor(renderer, jobs, options, hbsService, mjmlService, pugService, ejsService, nunjucksService, etaService, mustacheService, liquidService) {
1793
+ this.#renderer = renderer;
1794
+ this.#jobs = jobs;
1276
1795
  this.#options = options;
1277
1796
  this.#hbsService = hbsService;
1278
1797
  this.#mjmlService = mjmlService;
@@ -1281,6 +1800,7 @@ let PuppeteerService = class PuppeteerService {
1281
1800
  this.#nunjucksService = nunjucksService;
1282
1801
  this.#etaService = etaService;
1283
1802
  this.#mustacheService = mustacheService;
1803
+ this.#liquidService = liquidService;
1284
1804
  }
1285
1805
  #limit = pLimit(3);
1286
1806
  async generatePdfFromHtml(html, options) {
@@ -1290,87 +1810,19 @@ let PuppeteerService = class PuppeteerService {
1290
1810
  const job = this.#limit(async () => {
1291
1811
  signal?.throwIfAborted();
1292
1812
  started = true;
1293
- const mergePuppeteerOptions = mergePuppeteerParameters(this.#options, options);
1294
- if (mergePuppeteerOptions.chromiumRevision !== void 0) Logger.warn("Using `chromiumRevision` is no longer supported since the puppeteer update.");
1295
- const headless = mergePuppeteerOptions.headless ?? true;
1296
- const executablePath = mergePuppeteerOptions.executablePath || void 0;
1297
- const args = mergePuppeteerOptions.extraPuppeteerArgs || [
1298
- "--autoplay-policy=user-gesture-required",
1299
- "--disable-background-networking",
1300
- "--disable-background-timer-throttling",
1301
- "--disable-backgrounding-occluded-windows",
1302
- "--disable-breakpad",
1303
- "--disable-client-side-phishing-detection",
1304
- "--disable-component-update",
1305
- "--disable-default-apps",
1306
- "--disable-dev-shm-usage",
1307
- "--disable-domain-reliability",
1308
- "--disable-extensions",
1309
- "--disable-features=AudioServiceOutOfProcess",
1310
- "--disable-hang-monitor",
1311
- "--disable-ipc-flooding-protection",
1312
- "--disable-notifications",
1313
- "--disable-offer-store-unmasked-wallet-cards",
1314
- "--disable-print-preview",
1315
- "--disable-prompt-on-repost",
1316
- "--disable-renderer-backgrounding",
1317
- "--disable-setuid-sandbox",
1318
- "--disable-speech-api",
1319
- "--disable-sync",
1320
- "--hide-scrollbars",
1321
- "--ignore-gpu-blacklist",
1322
- "--metrics-recording-only",
1323
- "--mute-audio",
1324
- "--no-default-browser-check",
1325
- "--no-first-run",
1326
- "--no-pings",
1327
- "--password-store=basic",
1328
- "--use-gl=swiftshader",
1329
- "--use-mock-keychain",
1330
- "--disable-accelerated-2d-canvas",
1331
- "--no-zygote",
1332
- "--disable-gpu"
1333
- ];
1334
- this.#browserService.markJobStarted();
1335
- let context = null;
1336
- let closing;
1337
- const onAbort = () => {
1338
- if (!context) return;
1339
- closing = context.close();
1340
- closing.catch(() => void 0);
1341
- };
1342
- signal?.addEventListener("abort", onAbort, { once: true });
1813
+ const mergedOptions = mergePuppeteerParameters(this.#options, options);
1814
+ this.#jobs.markJobStarted();
1343
1815
  try {
1344
- context = await this.#browserService.createContext(args, headless, executablePath);
1816
+ const pdf = await this.#renderer.render(html, mergedOptions, signal);
1345
1817
  signal?.throwIfAborted();
1346
- const page = await context.newPage();
1347
- if (mergePuppeteerOptions.security) await installRequestGuard(context, page, mergePuppeteerOptions.security);
1348
- const pdfOptions = mergePuppeteerOptions.pdfOptions ?? { format: "A4" };
1349
- await page.setContent(html, { waitUntil: "domcontentloaded" });
1350
- await page.emulateMediaType("screen");
1351
- await page.waitForNetworkIdle({
1352
- idleTime: 500,
1353
- timeout: 3e4
1354
- });
1355
- /* v8 ignore next */
1356
- await page.evaluate(() => document.fonts.ready);
1357
- const pdf = await page.pdf(pdfOptions);
1358
- signal?.throwIfAborted();
1359
- return mergePuppeteerOptions.watermark ? await addWatermark(pdf, mergePuppeteerOptions.watermark) : pdf;
1818
+ return mergedOptions.watermark ? await addWatermark(pdf, mergedOptions.watermark) : pdf;
1360
1819
  } catch (e) {
1361
1820
  if (signal?.aborted) throw signal.reason;
1362
1821
  Logger.error(e);
1363
1822
  if (e instanceof NestjsPdfException) throw e;
1364
1823
  throw new PdfGenerationException(`PDF generation failed: ${String(e)}`, { cause: e });
1365
1824
  } finally {
1366
- signal?.removeEventListener("abort", onAbort);
1367
- if (context) try {
1368
- if (closing) await closing;
1369
- else await context.close();
1370
- } catch (error) {
1371
- Logger.error(error);
1372
- }
1373
- await this.#browserService.markJobFinished();
1825
+ await this.#jobs.markJobFinished();
1374
1826
  }
1375
1827
  });
1376
1828
  if (!signal) return job;
@@ -1443,19 +1895,31 @@ let PuppeteerService = class PuppeteerService {
1443
1895
  const html = requireService(this.#mustacheService, "Mustache").renderFile(file, data, options?.mustacheOptions ?? this.#options.mustacheOptions);
1444
1896
  return this.generatePdfFromHtml(html, options);
1445
1897
  }
1898
+ async generatePdfFromLiquidString(template, data = {}, options) {
1899
+ const html = requireService(this.#liquidService, "Liquid").render(template, data, options?.liquidOptions ?? this.#options.liquidOptions);
1900
+ return this.generatePdfFromHtml(html, options);
1901
+ }
1902
+ async generatePdfFromLiquidFile(file, data = {}, options) {
1903
+ const html = requireService(this.#liquidService, "Liquid").renderFile(file, data, options?.liquidOptions ?? this.#options.liquidOptions);
1904
+ return this.generatePdfFromHtml(html, options);
1905
+ }
1446
1906
  };
1447
1907
  PuppeteerService = __decorate([
1448
1908
  Injectable(),
1449
- __decorateParam(1, Inject(PDF_PARAMETERS)),
1450
- __decorateParam(2, Optional()),
1909
+ __decorateParam(0, Inject(PDF_RENDERER)),
1910
+ __decorateParam(1, Inject(BrowserService)),
1911
+ __decorateParam(2, Inject(PDF_PARAMETERS)),
1451
1912
  __decorateParam(3, Optional()),
1452
1913
  __decorateParam(4, Optional()),
1453
1914
  __decorateParam(5, Optional()),
1454
1915
  __decorateParam(6, Optional()),
1455
1916
  __decorateParam(7, Optional()),
1456
1917
  __decorateParam(8, Optional()),
1918
+ __decorateParam(9, Optional()),
1919
+ __decorateParam(10, Optional()),
1457
1920
  __decorateMetadata("design:paramtypes", [
1458
- typeof BrowserService === "undefined" ? Object : BrowserService,
1921
+ Object,
1922
+ Object,
1459
1923
  Object,
1460
1924
  typeof HandlebarsService === "undefined" ? Object : HandlebarsService,
1461
1925
  typeof MjmlService === "undefined" ? Object : MjmlService,
@@ -1463,11 +1927,11 @@ PuppeteerService = __decorate([
1463
1927
  typeof EjsService === "undefined" ? Object : EjsService,
1464
1928
  typeof NunjucksService === "undefined" ? Object : NunjucksService,
1465
1929
  typeof EtaService === "undefined" ? Object : EtaService,
1466
- typeof MustacheService === "undefined" ? Object : MustacheService
1930
+ typeof MustacheService === "undefined" ? Object : MustacheService,
1931
+ typeof LiquidService === "undefined" ? Object : LiquidService
1467
1932
  ])
1468
1933
  ], PuppeteerService);
1469
- //#endregion
1470
- //#region src/nestjs-pdf.service.ts
1934
+ // src/nestjs-pdf.service.ts
1471
1935
  let NestjsPdfService = class NestjsPdfService {
1472
1936
  #puppeteerService;
1473
1937
  constructor(puppeteerService) {
@@ -1621,6 +2085,26 @@ let NestjsPdfService = class NestjsPdfService {
1621
2085
  return this.#puppeteerService.generatePdfFromMustacheFile(file, data, options);
1622
2086
  }
1623
2087
  /**
2088
+ * @param template Liquid template string
2089
+ * @param data Data to inject into the template
2090
+ * @param options PDF generation options (optional)
2091
+ * @returns PDF generated from the provided Liquid template with data applied
2092
+ * @description This method generates a PDF from a Liquid template provided as a string. Liquid is a safe, Shopify-compatible template language with filters, tags, partials and layouts. The data to inject into the template is passed in the `data` object, which is used to render the template before converting it to PDF. PDF generation options can be customized by passing a `PuppeteerParameters` object as the third argument.
2093
+ */
2094
+ generatePdfFromLiquidString(template, data = {}, options) {
2095
+ return this.#puppeteerService.generatePdfFromLiquidString(template, data, options);
2096
+ }
2097
+ /**
2098
+ * @param file Path to the Liquid template file
2099
+ * @param data Data to inject into the template
2100
+ * @param options PDF generation options (optional)
2101
+ * @returns PDF generated from the Liquid template at the provided file path with data applied
2102
+ * @description This method generates a PDF from a Liquid template located at the specified file path. The file path is passed as a string in the `file` parameter. The data to inject into the template is passed in the `data` object, which is used to render the template before converting it to PDF. PDF generation options can be customized by passing a `PuppeteerParameters` object as the third argument.
2103
+ */
2104
+ generatePdfFromLiquidFile(file, data = {}, options) {
2105
+ return this.#puppeteerService.generatePdfFromLiquidFile(file, data, options);
2106
+ }
2107
+ /**
1624
2108
  * @param pdf The PDF bytes to modify
1625
2109
  * @param fieldName The name of the signature field to add (default: 'SignatureDebtor')
1626
2110
  * @param anchorText The text anchor to locate the signature field position (default: '__SIG_DEBTOR_ANCHOR__')
@@ -1667,9 +2151,14 @@ let NestjsPdfService = class NestjsPdfService {
1667
2151
  }
1668
2152
  };
1669
2153
  NestjsPdfService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof PuppeteerService === "undefined" ? Object : PuppeteerService])], NestjsPdfService);
1670
- //#endregion
1671
- //#region src/puppeteer/puppeteer.module.ts
2154
+ // src/puppeteer/puppeteer.module.ts
1672
2155
  var _PuppeteerModule;
2156
+ /** Builds the renderer of the configured provider, once the parameters are known. */
2157
+ const pdfRendererProvider = {
2158
+ provide: PDF_RENDERER,
2159
+ inject: [PDF_PARAMETERS, BrowserService],
2160
+ useFactory: createPdfRenderer
2161
+ };
1673
2162
  let PuppeteerModule = _PuppeteerModule = class PuppeteerModule {
1674
2163
  #browserService;
1675
2164
  #pdfParams;
@@ -1678,7 +2167,12 @@ let PuppeteerModule = _PuppeteerModule = class PuppeteerModule {
1678
2167
  this.#pdfParams = pdfParams;
1679
2168
  }
1680
2169
  async onModuleInit() {
1681
- if (!this.#pdfParams.executablePath) await this.#browserService.install(this.#pdfParams.useLockedBrowser ?? false);
2170
+ if (!usesPuppeteer(this.#pdfParams)) return;
2171
+ await loadPuppeteer();
2172
+ if (!this.#pdfParams.executablePath) {
2173
+ await loadPuppeteerBrowsers();
2174
+ await this.#browserService.install(this.#pdfParams.useLockedBrowser ?? false);
2175
+ }
1682
2176
  }
1683
2177
  static forRoot(pdfParameters) {
1684
2178
  return {
@@ -1694,7 +2188,8 @@ let PuppeteerModule = _PuppeteerModule = class PuppeteerModule {
1694
2188
  EjsService,
1695
2189
  NunjucksService,
1696
2190
  EtaService,
1697
- MustacheService
2191
+ MustacheService,
2192
+ LiquidService
1698
2193
  ],
1699
2194
  exports: [
1700
2195
  PuppeteerService,
@@ -1703,7 +2198,8 @@ let PuppeteerModule = _PuppeteerModule = class PuppeteerModule {
1703
2198
  EjsService,
1704
2199
  NunjucksService,
1705
2200
  EtaService,
1706
- MustacheService
2201
+ MustacheService,
2202
+ LiquidService
1707
2203
  ]
1708
2204
  };
1709
2205
  }
@@ -1720,13 +2216,15 @@ let PuppeteerModule = _PuppeteerModule = class PuppeteerModule {
1720
2216
  pdfParamsProvider,
1721
2217
  PuppeteerService,
1722
2218
  BrowserService,
2219
+ pdfRendererProvider,
1723
2220
  HandlebarsService,
1724
2221
  MjmlService,
1725
2222
  PugService,
1726
2223
  EjsService,
1727
2224
  NunjucksService,
1728
2225
  EtaService,
1729
- MustacheService
2226
+ MustacheService,
2227
+ LiquidService
1730
2228
  ],
1731
2229
  exports: [
1732
2230
  PuppeteerService,
@@ -1735,7 +2233,8 @@ let PuppeteerModule = _PuppeteerModule = class PuppeteerModule {
1735
2233
  EjsService,
1736
2234
  NunjucksService,
1737
2235
  EtaService,
1738
- MustacheService
2236
+ MustacheService,
2237
+ LiquidService
1739
2238
  ]
1740
2239
  };
1741
2240
  }
@@ -1745,12 +2244,14 @@ PuppeteerModule = _PuppeteerModule = __decorate([
1745
2244
  providers: [
1746
2245
  PuppeteerService,
1747
2246
  BrowserService,
2247
+ pdfRendererProvider,
1748
2248
  MjmlService,
1749
2249
  PugService,
1750
2250
  EjsService,
1751
2251
  NunjucksService,
1752
2252
  EtaService,
1753
- MustacheService
2253
+ MustacheService,
2254
+ LiquidService
1754
2255
  ],
1755
2256
  exports: [
1756
2257
  PuppeteerService,
@@ -1759,14 +2260,15 @@ PuppeteerModule = _PuppeteerModule = __decorate([
1759
2260
  EjsService,
1760
2261
  NunjucksService,
1761
2262
  EtaService,
1762
- MustacheService
2263
+ MustacheService,
2264
+ LiquidService
1763
2265
  ]
1764
2266
  }),
2267
+ __decorateParam(0, Inject(BrowserService)),
1765
2268
  __decorateParam(1, Inject(PDF_PARAMETERS)),
1766
- __decorateMetadata("design:paramtypes", [typeof BrowserService === "undefined" ? Object : BrowserService, Object])
2269
+ __decorateMetadata("design:paramtypes", [Object, Object])
1767
2270
  ], PuppeteerModule);
1768
- //#endregion
1769
- //#region src/nestjs-pdf.module.ts
2271
+ // src/nestjs-pdf.module.ts
1770
2272
  var _NestjsPdfModule;
1771
2273
  let NestjsPdfModule = _NestjsPdfModule = class NestjsPdfModule {
1772
2274
  static forRoot(options) {
@@ -1787,7 +2289,6 @@ let NestjsPdfModule = _NestjsPdfModule = class NestjsPdfModule {
1787
2289
  }
1788
2290
  };
1789
2291
  NestjsPdfModule = _NestjsPdfModule = __decorate([Module({})], NestjsPdfModule);
1790
- //#endregion
1791
- export { BrowserInstallationException, BrowserUnavailableException, EjsService, EngineNotAvailableException, EtaService, HandlebarsService, MjmlService, MustacheService, NestjsPdfErrorCode, NestjsPdfException, NestjsPdfModule, NestjsPdfService, NunjucksService, PdfGenerationException, PugService, PuppeteerModule, PuppeteerService, SignatureDependencyException, SignaturePlacementException, TemplateConfigurationException, TemplateRenderException, WatermarkException, getRelativePathToValue };
2292
+ export { BrowserInstallationException, BrowserUnavailableException, EjsService, EngineNotAvailableException, EtaService, HandlebarsService, LiquidService, MjmlService, MustacheService, NestjsPdfErrorCode, NestjsPdfException, NestjsPdfModule, NestjsPdfService, NunjucksService, PdfGenerationException, PugService, PuppeteerModule, PuppeteerService, RendererConfigurationException, SignatureDependencyException, SignaturePlacementException, TemplateConfigurationException, TemplateRenderException, WatermarkException, getRelativePathToValue };
1792
2293
 
1793
2294
  //# sourceMappingURL=index.mjs.map