@json-to-office/jto 0.15.0 → 0.17.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 (30) hide show
  1. package/dist/cli.js +130 -37
  2. package/dist/cli.js.map +1 -1
  3. package/dist/client/assets/{HomePage-BTmw2QAf.js → HomePage-DMh-RgIS.js} +3 -3
  4. package/dist/client/assets/{HomePage-BTmw2QAf.js.map → HomePage-DMh-RgIS.js.map} +1 -1
  5. package/dist/client/assets/{JsonEditorPage-CRONWw-J.js → JsonEditorPage-D1guKBA7.js} +3 -3
  6. package/dist/client/assets/{JsonEditorPage-CRONWw-J.js.map → JsonEditorPage-D1guKBA7.js.map} +1 -1
  7. package/dist/client/assets/{MonacoPluginProvider-DhOIgTne.js → MonacoPluginProvider-BGk556YV.js} +3 -3
  8. package/dist/client/assets/{MonacoPluginProvider-DhOIgTne.js.map → MonacoPluginProvider-BGk556YV.js.map} +1 -1
  9. package/dist/client/assets/{button-ebCBEwlw.js → button-BcpACLlK.js} +2 -2
  10. package/dist/client/assets/{button-ebCBEwlw.js.map → button-BcpACLlK.js.map} +1 -1
  11. package/dist/client/assets/{editor-CB9BVJGn.js → editor-DuF2Zy4-.js} +2 -2
  12. package/dist/client/assets/{editor-CB9BVJGn.js.map → editor-DuF2Zy4-.js.map} +1 -1
  13. package/dist/client/assets/editor-monaco-json-BmSqeB6D.js +24 -0
  14. package/dist/client/assets/editor-monaco-json-BmSqeB6D.js.map +1 -0
  15. package/dist/client/assets/index-Auo5vP51.css +1 -0
  16. package/dist/client/assets/index-Cx5kWIBL.js +5 -0
  17. package/dist/client/assets/index-Cx5kWIBL.js.map +1 -0
  18. package/dist/client/assets/{preview-B4bcm38l.js → preview-WR-KEQG4.js} +2 -2
  19. package/dist/client/assets/{preview-B4bcm38l.js.map → preview-WR-KEQG4.js.map} +1 -1
  20. package/dist/client/index.html +2 -2
  21. package/dist/index.d.ts +1 -0
  22. package/dist/render-server.d.ts +2 -0
  23. package/dist/render-server.js +255 -0
  24. package/dist/render-server.js.map +1 -0
  25. package/package.json +9 -9
  26. package/dist/client/assets/editor-monaco-json-DaGk8WIJ.js +0 -6
  27. package/dist/client/assets/editor-monaco-json-DaGk8WIJ.js.map +0 -1
  28. package/dist/client/assets/index-Cy63RxyT.js +0 -5
  29. package/dist/client/assets/index-Cy63RxyT.js.map +0 -1
  30. package/dist/client/assets/index-DiQf3mCW.css +0 -1
package/dist/cli.js CHANGED
@@ -1517,17 +1517,99 @@ var init_rate_limit = __esm({
1517
1517
  }
1518
1518
  });
1519
1519
 
1520
+ // src/server/rasterize-route.ts
1521
+ import { Type as Type2 } from "@sinclair/typebox";
1522
+ import { bodyLimit } from "hono/body-limit";
1523
+ import { HTTPException as HTTPException3 } from "hono/http-exception";
1524
+ import {
1525
+ clampVisualDpi,
1526
+ DEFAULT_VISUAL_DPI,
1527
+ MIN_VISUAL_DPI,
1528
+ MAX_VISUAL_DPI
1529
+ } from "@json-to-office/shared";
1530
+ import { createLibreOfficePptxRasterizer } from "@json-to-office/jto-cli";
1531
+ function getSharedRasterizer() {
1532
+ if (!sharedRasterizer) {
1533
+ sharedRasterizer = createLibreOfficePptxRasterizer();
1534
+ }
1535
+ return sharedRasterizer;
1536
+ }
1537
+ function registerRasterizeRoute(router, options = {}) {
1538
+ const getRasterizer = options.getRasterizer ?? getSharedRasterizer;
1539
+ router.post(
1540
+ "/rasterize",
1541
+ ...options.preMiddleware ?? [],
1542
+ bodyLimit({
1543
+ maxSize: 32 * 1024 * 1024,
1544
+ onError: () => {
1545
+ throw new HTTPException3(413, { message: "Request body too large" });
1546
+ }
1547
+ }),
1548
+ jsonOnly,
1549
+ tbValidator(RasterizeRequestSchema),
1550
+ async (c) => {
1551
+ const { presentation, dpi } = getValidated(c, "json");
1552
+ try {
1553
+ const result = await getRasterizer()({
1554
+ presentation,
1555
+ dpi: clampVisualDpi(dpi ?? DEFAULT_VISUAL_DPI)
1556
+ });
1557
+ return c.json(result);
1558
+ } catch (error) {
1559
+ options.onError?.(error);
1560
+ if (error instanceof HTTPException3) throw error;
1561
+ const msg = error instanceof Error ? error.message.toLowerCase() : String(error);
1562
+ if (msg.includes("not found") || msg.includes("rasterization needs")) {
1563
+ throw new HTTPException3(503, { message: error.message });
1564
+ }
1565
+ if (msg.includes("invalid") || msg.includes("validation")) {
1566
+ throw new HTTPException3(400, { message: error.message });
1567
+ }
1568
+ throw new HTTPException3(500, {
1569
+ message: "Internal server error during rasterization"
1570
+ });
1571
+ }
1572
+ }
1573
+ );
1574
+ }
1575
+ var RasterizeRequestSchema, sharedRasterizer, jsonOnly;
1576
+ var init_rasterize_route = __esm({
1577
+ "src/server/rasterize-route.ts"() {
1578
+ "use strict";
1579
+ init_esm_shims();
1580
+ init_typebox_validator();
1581
+ RasterizeRequestSchema = Type2.Object(
1582
+ {
1583
+ presentation: Type2.Object({}, { additionalProperties: true }),
1584
+ dpi: Type2.Optional(
1585
+ Type2.Number({ minimum: MIN_VISUAL_DPI, maximum: MAX_VISUAL_DPI })
1586
+ )
1587
+ },
1588
+ { additionalProperties: false }
1589
+ );
1590
+ jsonOnly = async (c, next) => {
1591
+ const contentType = c.req.header("content-type");
1592
+ if (!contentType || !contentType.includes("application/json")) {
1593
+ throw new HTTPException3(400, {
1594
+ message: "Content-Type must be application/json"
1595
+ });
1596
+ }
1597
+ await next();
1598
+ };
1599
+ }
1600
+ });
1601
+
1520
1602
  // src/server/routes/format.ts
1521
1603
  import { Hono as Hono2 } from "hono";
1522
- import { HTTPException as HTTPException3 } from "hono/http-exception";
1523
- import { bodyLimit } from "hono/body-limit";
1604
+ import { HTTPException as HTTPException4 } from "hono/http-exception";
1605
+ import { bodyLimit as bodyLimit2 } from "hono/body-limit";
1524
1606
  import { PluginRegistry as PluginRegistry2 } from "@json-to-office/jto-cli";
1525
1607
  function createFormatRouter(adapter) {
1526
1608
  const router = new Hono2();
1527
1609
  const contentTypeMw = async (c, next) => {
1528
1610
  const contentType = c.req.header("content-type");
1529
1611
  if (!contentType || !contentType.includes("application/json")) {
1530
- throw new HTTPException3(400, {
1612
+ throw new HTTPException4(400, {
1531
1613
  message: "Content-Type must be application/json"
1532
1614
  });
1533
1615
  }
@@ -1591,11 +1673,11 @@ function createFormatRouter(adapter) {
1591
1673
  if (error instanceof Error) {
1592
1674
  const msg = error.message.toLowerCase();
1593
1675
  if (msg.includes("invalid") || msg.includes("validation") || msg.includes("missing required") || msg.includes("unknown component")) {
1594
- throw new HTTPException3(400, { message: error.message });
1676
+ throw new HTTPException4(400, { message: error.message });
1595
1677
  }
1596
1678
  }
1597
- if (error instanceof HTTPException3) throw error;
1598
- throw new HTTPException3(500, {
1679
+ if (error instanceof HTTPException4) throw error;
1680
+ throw new HTTPException4(500, {
1599
1681
  message: `Internal server error during ${adapter.label} generation`
1600
1682
  });
1601
1683
  }
@@ -1628,12 +1710,12 @@ function createFormatRouter(adapter) {
1628
1710
  if (adapter.name === "docx") {
1629
1711
  router.post(
1630
1712
  "/diff",
1631
- bodyLimit({
1713
+ bodyLimit2({
1632
1714
  // Two full documents per request — same cap rationale as
1633
1715
  // /preview/libreoffice-from-json, doubled.
1634
1716
  maxSize: 32 * 1024 * 1024,
1635
1717
  onError: () => {
1636
- throw new HTTPException3(413, { message: "Request body too large" });
1718
+ throw new HTTPException4(413, { message: "Request body too large" });
1637
1719
  }
1638
1720
  }),
1639
1721
  rateLimiter({
@@ -1651,7 +1733,7 @@ function createFormatRouter(adapter) {
1651
1733
  try {
1652
1734
  return JSON.parse(def);
1653
1735
  } catch {
1654
- throw new HTTPException3(400, {
1736
+ throw new HTTPException4(400, {
1655
1737
  message: `${label} document is not valid JSON`
1656
1738
  });
1657
1739
  }
@@ -1681,7 +1763,7 @@ function createFormatRouter(adapter) {
1681
1763
  }
1682
1764
  const revisionDate = options?.date ? new Date(options.date) : /* @__PURE__ */ new Date();
1683
1765
  if (isNaN(revisionDate.getTime())) {
1684
- throw new HTTPException3(400, {
1766
+ throw new HTTPException4(400, {
1685
1767
  message: `Invalid date: "${options?.date}" (expected ISO 8601)`
1686
1768
  });
1687
1769
  }
@@ -1700,12 +1782,12 @@ function createFormatRouter(adapter) {
1700
1782
  meta: { timestamp: (/* @__PURE__ */ new Date()).toISOString(), requestId }
1701
1783
  });
1702
1784
  } catch (error) {
1703
- if (error instanceof HTTPException3) throw error;
1785
+ if (error instanceof HTTPException4) throw error;
1704
1786
  logger.error("Document diff failed", { error, requestId });
1705
1787
  if (error instanceof Error && error.message.includes("top-level component")) {
1706
- throw new HTTPException3(400, { message: error.message });
1788
+ throw new HTTPException4(400, { message: error.message });
1707
1789
  }
1708
- throw new HTTPException3(500, {
1790
+ throw new HTTPException4(500, {
1709
1791
  message: "Internal server error during document diff"
1710
1792
  });
1711
1793
  }
@@ -1728,12 +1810,12 @@ function createFormatRouter(adapter) {
1728
1810
  const body = await c.req.parseBody();
1729
1811
  const file = body.file;
1730
1812
  if (!file || typeof file === "string") {
1731
- throw new HTTPException3(400, {
1813
+ throw new HTTPException4(400, {
1732
1814
  message: `No ${adapter.name.toUpperCase()} file provided`
1733
1815
  });
1734
1816
  }
1735
1817
  if (file.size === 0) {
1736
- throw new HTTPException3(400, {
1818
+ throw new HTTPException4(400, {
1737
1819
  message: `${adapter.name.toUpperCase()} file is empty`
1738
1820
  });
1739
1821
  }
@@ -1753,18 +1835,18 @@ function createFormatRouter(adapter) {
1753
1835
  error,
1754
1836
  requestId
1755
1837
  });
1756
- if (error instanceof HTTPException3) throw error;
1838
+ if (error instanceof HTTPException4) throw error;
1757
1839
  if (error instanceof LibreOfficeBinaryNotFoundError) {
1758
- throw new HTTPException3(503, {
1840
+ throw new HTTPException4(503, {
1759
1841
  message: "LibreOffice is not available. Install LibreOffice or set LIBREOFFICE_PATH."
1760
1842
  });
1761
1843
  }
1762
1844
  if (error instanceof LibreOfficeTimeoutError || error instanceof LibreOfficeConversionError || error instanceof LibreOfficeOutputNotFoundError) {
1763
- throw new HTTPException3(500, {
1845
+ throw new HTTPException4(500, {
1764
1846
  message: "LibreOffice preview conversion failed."
1765
1847
  });
1766
1848
  }
1767
- throw new HTTPException3(500, {
1849
+ throw new HTTPException4(500, {
1768
1850
  message: "Internal server error during preview conversion"
1769
1851
  });
1770
1852
  }
@@ -1772,13 +1854,13 @@ function createFormatRouter(adapter) {
1772
1854
  );
1773
1855
  router.post(
1774
1856
  "/preview/libreoffice-from-json",
1775
- bodyLimit({
1857
+ bodyLimit2({
1776
1858
  // Doc JSON + custom themes. 16 MB accommodates real-world docs that
1777
1859
  // inline base64 image assets (logos, screenshots, chart images); the
1778
1860
  // earlier 2 MB cap rejected legitimate payloads with 413.
1779
1861
  maxSize: 16 * 1024 * 1024,
1780
1862
  onError: () => {
1781
- throw new HTTPException3(413, { message: "Request body too large" });
1863
+ throw new HTTPException4(413, { message: "Request body too large" });
1782
1864
  }
1783
1865
  }),
1784
1866
  rateLimiter({
@@ -1816,18 +1898,18 @@ function createFormatRouter(adapter) {
1816
1898
  error,
1817
1899
  requestId
1818
1900
  });
1819
- if (error instanceof HTTPException3) throw error;
1901
+ if (error instanceof HTTPException4) throw error;
1820
1902
  if (error instanceof LibreOfficeBinaryNotFoundError) {
1821
- throw new HTTPException3(503, {
1903
+ throw new HTTPException4(503, {
1822
1904
  message: "LibreOffice is not available. Install LibreOffice or set LIBREOFFICE_PATH."
1823
1905
  });
1824
1906
  }
1825
1907
  if (error instanceof LibreOfficeTimeoutError || error instanceof LibreOfficeConversionError || error instanceof LibreOfficeOutputNotFoundError) {
1826
- throw new HTTPException3(500, {
1908
+ throw new HTTPException4(500, {
1827
1909
  message: "LibreOffice preview conversion failed."
1828
1910
  });
1829
1911
  }
1830
- throw new HTTPException3(500, {
1912
+ throw new HTTPException4(500, {
1831
1913
  message: "Internal server error during preview conversion"
1832
1914
  });
1833
1915
  }
@@ -1867,8 +1949,8 @@ function createFormatRouter(adapter) {
1867
1949
  error,
1868
1950
  requestId
1869
1951
  });
1870
- if (error instanceof HTTPException3) throw error;
1871
- throw new HTTPException3(500, {
1952
+ if (error instanceof HTTPException4) throw error;
1953
+ throw new HTTPException4(500, {
1872
1954
  message: "Failed to get standard components definition"
1873
1955
  });
1874
1956
  }
@@ -1896,7 +1978,7 @@ function createFormatRouter(adapter) {
1896
1978
  });
1897
1979
  } catch (error) {
1898
1980
  logger.error("Failed to get cache statistics", { error });
1899
- throw new HTTPException3(500, {
1981
+ throw new HTTPException4(500, {
1900
1982
  message: "Failed to get cache statistics"
1901
1983
  });
1902
1984
  }
@@ -1918,7 +2000,7 @@ function createFormatRouter(adapter) {
1918
2000
  });
1919
2001
  } catch (error) {
1920
2002
  logger.error("Failed to get cache analytics", { error });
1921
- throw new HTTPException3(500, {
2003
+ throw new HTTPException4(500, {
1922
2004
  message: "Failed to get cache analytics"
1923
2005
  });
1924
2006
  }
@@ -1936,9 +2018,19 @@ function createFormatRouter(adapter) {
1936
2018
  });
1937
2019
  } catch (error) {
1938
2020
  logger.error("Failed to clear cache", { error });
1939
- throw new HTTPException3(500, { message: "Failed to clear cache" });
2021
+ throw new HTTPException4(500, { message: "Failed to clear cache" });
1940
2022
  }
1941
2023
  });
2024
+ registerRasterizeRoute(router, {
2025
+ preMiddleware: [
2026
+ rateLimiter({
2027
+ limit: process.env.NODE_ENV === "production" ? 10 : 1e3,
2028
+ window: 15 * 60 * 1e3,
2029
+ keyGenerator: (c) => c.req.header("X-Real-IP") || c.req.header("X-Forwarded-For")?.split(",").pop()?.trim() || "anonymous"
2030
+ })
2031
+ ],
2032
+ onError: (error) => logger.error("Visual rasterization failed", { error })
2033
+ });
1942
2034
  return router;
1943
2035
  }
1944
2036
  var init_format = __esm({
@@ -1950,6 +2042,7 @@ var init_format = __esm({
1950
2042
  init_typebox_validator();
1951
2043
  init_logger();
1952
2044
  init_rate_limit();
2045
+ init_rasterize_route();
1953
2046
  init_libreoffice_converter();
1954
2047
  }
1955
2048
  });
@@ -2712,8 +2805,8 @@ var init_ai = __esm({
2712
2805
  // src/server/routes/fonts.ts
2713
2806
  import { createHash } from "crypto";
2714
2807
  import { Hono as Hono5 } from "hono";
2715
- import { HTTPException as HTTPException4 } from "hono/http-exception";
2716
- import { bodyLimit as bodyLimit2 } from "hono/body-limit";
2808
+ import { HTTPException as HTTPException5 } from "hono/http-exception";
2809
+ import { bodyLimit as bodyLimit3 } from "hono/body-limit";
2717
2810
  import {
2718
2811
  SAFE_FONTS,
2719
2812
  POPULAR_GOOGLE_FONTS as POPULAR_GOOGLE_FONTS2,
@@ -2747,11 +2840,11 @@ var init_fonts = __esm({
2747
2840
  });
2748
2841
  fontsRouter.post(
2749
2842
  "/materialize",
2750
- bodyLimit2({
2843
+ bodyLimit3({
2751
2844
  // Body is just {family, weights, italics} — 16 KB is generous.
2752
2845
  maxSize: 16 * 1024,
2753
2846
  onError: () => {
2754
- throw new HTTPException4(413, { message: "Request body too large" });
2847
+ throw new HTTPException5(413, { message: "Request body too large" });
2755
2848
  }
2756
2849
  }),
2757
2850
  rateLimiter({
@@ -2867,7 +2960,7 @@ var init_auth = __esm({
2867
2960
  });
2868
2961
 
2869
2962
  // src/server/middleware/hono/error-handler.ts
2870
- import { HTTPException as HTTPException5 } from "hono/http-exception";
2963
+ import { HTTPException as HTTPException6 } from "hono/http-exception";
2871
2964
  var errorHandler;
2872
2965
  var init_error_handler = __esm({
2873
2966
  "src/server/middleware/hono/error-handler.ts"() {
@@ -2877,7 +2970,7 @@ var init_error_handler = __esm({
2877
2970
  init_config();
2878
2971
  errorHandler = (err, c) => {
2879
2972
  const requestId = c.get("requestId") || "unknown";
2880
- if (err instanceof HTTPException5) {
2973
+ if (err instanceof HTTPException6) {
2881
2974
  return c.json(
2882
2975
  {
2883
2976
  success: false,
@@ -3463,7 +3556,7 @@ ${chalk.cyan("Health:")} ${url}/health
3463
3556
  }
3464
3557
 
3465
3558
  // src/cli.ts
3466
- var PACKAGE_VERSION = true ? "0.15.0" : "dev-mode";
3559
+ var PACKAGE_VERSION = true ? "0.17.0" : "dev-mode";
3467
3560
  var program = new Command2();
3468
3561
  program.name("jto").description("JSON to Office CLI - Generate .docx and .pptx from JSON").version(PACKAGE_VERSION);
3469
3562
  registerCoreCommands(program, {