@ai-matrx/alchemy 0.18.0 → 0.19.1
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/CHANGELOG.md +16 -0
- package/dist/core.d.cts +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/operate-capture.cjs +1 -1
- package/dist/operate-capture.cjs.map +1 -1
- package/dist/operate-capture.d.cts +13 -56
- package/dist/operate-capture.d.ts +13 -56
- package/dist/operate-capture.js +1 -1
- package/dist/operate-capture.js.map +1 -1
- package/dist/operate-document.d.cts +1 -1
- package/dist/operate-document.d.ts +1 -1
- package/dist/operate-read.cjs +1 -5
- package/dist/operate-read.cjs.map +1 -1
- package/dist/operate-read.d.cts +5 -49
- package/dist/operate-read.d.ts +5 -49
- package/dist/operate-read.js +1 -5
- package/dist/operate-read.js.map +1 -1
- package/dist/operate-rich-html.d.cts +1 -1
- package/dist/operate-rich-html.d.ts +1 -1
- package/dist/operate-xlsx.cjs +2 -3
- package/dist/operate-xlsx.cjs.map +1 -1
- package/dist/operate-xlsx.d.cts +3 -3
- package/dist/operate-xlsx.d.ts +3 -3
- package/dist/operate-xlsx.js +2 -3
- package/dist/operate-xlsx.js.map +1 -1
- package/dist/operate.d.cts +1 -1
- package/dist/operate.d.ts +1 -1
- package/dist/react-workspace.cjs +1 -1
- package/dist/react-workspace.cjs.map +1 -1
- package/dist/react-workspace.d.cts +6 -0
- package/dist/react-workspace.d.ts +6 -0
- package/dist/react-workspace.js +1 -1
- package/dist/react-workspace.js.map +1 -1
- package/package.json +3 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.19.1
|
|
4
|
+
### Split-Copy hosts can drop the two tiles they already show
|
|
5
|
+
|
|
6
|
+
- `MatrxCopyMenu` accepts `richCopyFlavors` (e.g. `[]`): a host that renders its own "Copy markdown" / "Copy plain text" rows (matrx-frontend `RichCopySplit`) no longer gets the palette repeating them. Formatted, JSON, download and AI stay once.
|
|
7
|
+
|
|
8
|
+
## 0.19.0
|
|
9
|
+
### One parser, one capture engine, one TSV writer (AP-2)
|
|
10
|
+
|
|
11
|
+
- `/operate/read` `parseDelimited` (and its types) is re-exported from `@ai-matrx/kit/delimited` (needs kit 0.31.0) — same API, one parser beside the one writer.
|
|
12
|
+
- `/operate/capture` `captureElement`, `pagesToPdf`, `elementsToPdf` load `@ai-matrx/print/pdf`'s `captureCanvas` / `pagesToPdf` (needs print 0.10.0): ONE html2canvas + jsPDF engine. Alchemy no longer depends on `html2canvas` or `jspdf`.
|
|
13
|
+
- The `xlsx-workbook` artifact's plain text is kit `toDelimited` TSV — quoted and formula-guarded (was a raw tab join).
|
|
14
|
+
- `spreadsheetSafeByDefault("xlsx")` is `false` (kit 0.31.0); the `xlsx` plain text still serializes as tsv, guarded. Text that is exactly a signed number is no longer prefixed.
|
|
15
|
+
- FEATURE.md records the engine ruling and the accepted vendor facts (pptx leaf's internal JSZip; SheetJS from its CDN tarball). The publish workflow now runs `check-bundled-deps.mjs --strict`.
|
|
16
|
+
|
|
17
|
+
**Consumer action:** none for `/operate/read` and `/operate/capture` callers. Install `@ai-matrx/print` ≥ 0.10.0 and `@ai-matrx/kit` ≥ 0.31.0 with it.
|
|
18
|
+
|
|
3
19
|
## 0.18.0
|
|
4
20
|
### One `spreadsheetSafe` default; one CSV/TSV writer; zip through print (AP-2)
|
|
5
21
|
|
package/dist/core.d.cts
CHANGED
|
@@ -1214,7 +1214,7 @@ type TransferOperation = {
|
|
|
1214
1214
|
type BuiltInFormatId = "plain" | "markdown" | "rich-html" | "html" | "json" | "compact-json" | "csv" | "tsv" | "xlsx" | "xlsx-workbook" | "pdf" | "docx" | "epub";
|
|
1215
1215
|
interface FormatOptions {
|
|
1216
1216
|
filename?: string;
|
|
1217
|
-
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv
|
|
1217
|
+
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv; an .xlsx's typed text cells are never evaluated, its plain text is the guarded TSV). */
|
|
1218
1218
|
spreadsheetSafe?: boolean;
|
|
1219
1219
|
/** Markdown→HTML skin: the two page looks plus "inline" for the rich clipboard (styles on elements). */
|
|
1220
1220
|
skin?: "document" | "article" | "inline";
|
package/dist/core.d.ts
CHANGED
|
@@ -1214,7 +1214,7 @@ type TransferOperation = {
|
|
|
1214
1214
|
type BuiltInFormatId = "plain" | "markdown" | "rich-html" | "html" | "json" | "compact-json" | "csv" | "tsv" | "xlsx" | "xlsx-workbook" | "pdf" | "docx" | "epub";
|
|
1215
1215
|
interface FormatOptions {
|
|
1216
1216
|
filename?: string;
|
|
1217
|
-
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv
|
|
1217
|
+
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv; an .xlsx's typed text cells are never evaluated, its plain text is the guarded TSV). */
|
|
1218
1218
|
spreadsheetSafe?: boolean;
|
|
1219
1219
|
/** Markdown→HTML skin: the two page looks plus "inline" for the rich clipboard (styles on elements). */
|
|
1220
1220
|
skin?: "document" | "article" | "inline";
|
package/dist/index.d.cts
CHANGED
|
@@ -1214,7 +1214,7 @@ type TransferOperation = {
|
|
|
1214
1214
|
type BuiltInFormatId = "plain" | "markdown" | "rich-html" | "html" | "json" | "compact-json" | "csv" | "tsv" | "xlsx" | "xlsx-workbook" | "pdf" | "docx" | "epub";
|
|
1215
1215
|
interface FormatOptions {
|
|
1216
1216
|
filename?: string;
|
|
1217
|
-
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv
|
|
1217
|
+
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv; an .xlsx's typed text cells are never evaluated, its plain text is the guarded TSV). */
|
|
1218
1218
|
spreadsheetSafe?: boolean;
|
|
1219
1219
|
/** Markdown→HTML skin: the two page looks plus "inline" for the rich clipboard (styles on elements). */
|
|
1220
1220
|
skin?: "document" | "article" | "inline";
|
package/dist/index.d.ts
CHANGED
|
@@ -1214,7 +1214,7 @@ type TransferOperation = {
|
|
|
1214
1214
|
type BuiltInFormatId = "plain" | "markdown" | "rich-html" | "html" | "json" | "compact-json" | "csv" | "tsv" | "xlsx" | "xlsx-workbook" | "pdf" | "docx" | "epub";
|
|
1215
1215
|
interface FormatOptions {
|
|
1216
1216
|
filename?: string;
|
|
1217
|
-
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv
|
|
1217
|
+
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv; an .xlsx's typed text cells are never evaluated, its plain text is the guarded TSV). */
|
|
1218
1218
|
spreadsheetSafe?: boolean;
|
|
1219
1219
|
/** Markdown→HTML skin: the two page looks plus "inline" for the rich clipboard (styles on elements). */
|
|
1220
1220
|
skin?: "document" | "article" | "inline";
|
package/dist/operate-capture.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var operate_capture_exports={};__export(operate_capture_exports,{canvasToBlob:()=>canvasToBlob,captureElement:()=>captureElement,elementToImage:()=>elementToImage,elementsToPdf:()=>elementsToPdf,pagesToPdf:()=>pagesToPdf,renderElement:()=>renderElement,renderElementToDataUrl:()=>renderElementToDataUrl});module.exports=__toCommonJS(operate_capture_exports);
|
|
1
|
+
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var operate_capture_exports={};__export(operate_capture_exports,{canvasToBlob:()=>canvasToBlob,captureElement:()=>captureElement,elementToImage:()=>elementToImage,elementsToPdf:()=>elementsToPdf,pagesToPdf:()=>pagesToPdf,renderElement:()=>renderElement,renderElementToDataUrl:()=>renderElementToDataUrl});module.exports=__toCommonJS(operate_capture_exports);async function captureElement(element,options={}){let{captureCanvas}=await import("@ai-matrx/print/pdf");return captureCanvas(element,options)}async function canvasToBlob(canvas,type="image/png",quality){let blob=await new Promise(resolve=>canvas.toBlob(resolve,type,quality));if(!blob)throw new Error("The image could not be encoded.");return blob}async function elementToImage(element,options={}){let{type,quality,...capture}=options;return canvasToBlob(await captureElement(element,capture),type,quality)}async function renderElement(element,options={}){let{toCanvas}=await import("html-to-image");return toCanvas(element,options)}async function renderElementToDataUrl(element,options={}){let{toPng}=await import("html-to-image");return toPng(element,options)}async function pagesToPdf(pages,options={}){let{pagesToPdf:layOut}=await import("@ai-matrx/print/pdf");return layOut(pages,options)}async function elementsToPdf(elements,options={}){let pages=[];for(let element of elements)pages.push({...options.page,image:await captureElement(element,options.capture)});return pagesToPdf(pages,options)}
|
|
2
2
|
//# sourceMappingURL=operate-capture.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/operate-capture.ts","../src/operate/capture.ts"],"sourcesContent":["/** `@ai-matrx/alchemy/operate/capture` — the one DOM capture engine (element → image / PDF). html2canvas, html-to-image and jsPDF load on first use. */\nexport {\n canvasToBlob,\n captureElement,\n elementToImage,\n elementsToPdf,\n pagesToPdf,\n renderElement,\n renderElementToDataUrl,\n type CaptureOptions,\n type PdfDocumentOptions,\n type PdfMargins,\n type PdfPage,\n type RenderOptions,\n} from \"./operate/capture\";\n","/**\n * `@ai-matrx/alchemy/operate/capture` — THE DOM capture engine (ALC-19 T11): an element on screen →\n * an image or a PDF, with nothing else in the app importing html2canvas or jsPDF. Both libraries\n * load on first use (`import()`), so a page that never exports never downloads them.\n *\n * Tailwind 4 colours (oklch/lab/color()) are not parseable by html2canvas, which logs an error per\n * value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.\n */\n\nexport type CaptureOptions = {\n /** Device-pixel scale of the capture (default 2 — legible when scaled in a deck or print). */\n scale?: number;\n /** Canvas background; null keeps transparency. */\n backgroundColor?: string | null;\n useCORS?: boolean;\n allowTaint?: boolean;\n logging?: boolean;\n width?: number;\n height?: number;\n x?: number;\n y?: number;\n scrollX?: number;\n scrollY?: number;\n windowWidth?: number;\n windowHeight?: number;\n ignoreElements?(element: Element): boolean;\n onclone?(document: Document, element: HTMLElement): void | Promise<void>;\n /** Replace unparseable modern colour functions while capturing (`{ dark }` picks the palette). */\n safeColors?: boolean | { dark: boolean };\n};\n\nconst UNSAFE_COLOR = /\\b(oklch|oklab|lab|lch|color)\\s*\\(/i;\n\nfunction safeColor(value: string, prop: string, dark: boolean): string {\n if (!UNSAFE_COLOR.test(value)) return value;\n const p = prop.toLowerCase();\n if (p.includes(\"background\")) return dark ? \"#1f2937\" : \"#ffffff\";\n if (p === \"color\") return dark ? \"#f3f4f6\" : \"#111827\";\n if (p.includes(\"border\") || p.includes(\"outline\")) return dark ? \"#4b5563\" : \"#d1d5db\";\n if (p === \"fill\" || p === \"stroke\") return dark ? \"#9ca3af\" : \"#374151\";\n return \"#6b7280\";\n}\n\n/** Patch `getComputedStyle` so the capture never reads an unsafe colour; returns the restore. */\nfunction patchComputedStyle(dark: boolean): () => void {\n const original = window.getComputedStyle;\n const bound = original.bind(window);\n window.getComputedStyle = function (elt: Element, pseudo?: string | null) {\n const computed = bound(elt, pseudo);\n return new Proxy(computed, {\n get(target, prop: string | symbol) {\n const value = (target as unknown as Record<string | symbol, unknown>)[prop];\n if (typeof prop === \"string\" && typeof value === \"string\" && value && UNSAFE_COLOR.test(value)) return safeColor(value, prop, dark);\n if (prop === \"getPropertyValue\")\n return (p: string) => {\n const raw = target.getPropertyValue(p);\n return raw && UNSAFE_COLOR.test(raw) ? safeColor(raw, p, dark) : raw;\n };\n if (typeof value === \"function\") return (value as CallableFunction).bind(target);\n return value;\n },\n });\n } as typeof window.getComputedStyle;\n return () => {\n window.getComputedStyle = original;\n };\n}\n\nconst prefersDark = () =>\n typeof document !== \"undefined\" &&\n (document.documentElement.classList.contains(\"dark\") || (typeof window.matchMedia === \"function\" && window.matchMedia(\"(prefers-color-scheme: dark)\").matches));\n\n/** Rasterise a live element to a canvas (what the person sees, at `scale`). */\nexport async function captureElement(element: HTMLElement, options: CaptureOptions = {}): Promise<HTMLCanvasElement> {\n const { safeColors, ...rest } = options;\n const { default: html2canvas } = await import(\"html2canvas\");\n const restore = safeColors ? patchComputedStyle(typeof safeColors === \"object\" ? safeColors.dark : prefersDark()) : () => {};\n try {\n return await html2canvas(element, { scale: 2, logging: false, useCORS: true, ...rest } as Parameters<typeof html2canvas>[1]);\n } finally {\n restore();\n }\n}\n\n/** Encode a canvas (PNG by default; JPEG/WebP take a 0–1 quality). Throws when the browser refuses. */\nexport async function canvasToBlob(canvas: HTMLCanvasElement, type: \"image/png\" | \"image/jpeg\" | \"image/webp\" = \"image/png\", quality?: number): Promise<Blob> {\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (!blob) throw new Error(\"The image could not be encoded.\");\n return blob;\n}\n\n/** Capture an element straight to an image Blob. */\nexport async function elementToImage(\n element: HTMLElement,\n options: CaptureOptions & { type?: \"image/png\" | \"image/jpeg\" | \"image/webp\"; quality?: number } = {},\n): Promise<Blob> {\n const { type, quality, ...capture } = options;\n return canvasToBlob(await captureElement(element, capture), type, quality);\n}\n\n/** Options for `renderElement` (html-to-image): the SVG foreignObject renderer. */\nexport type RenderOptions = {\n /** Device-pixel ratio (default 1). */\n pixelRatio?: number;\n width?: number;\n height?: number;\n /** Final canvas size; the render is scaled to it. */\n canvasWidth?: number;\n canvasHeight?: number;\n backgroundColor?: string;\n /** Inline style applied to the cloned root (e.g. a translate to pad the chart). */\n style?: Partial<CSSStyleDeclaration>;\n /** Return false to leave a node out of the picture. */\n filter?(node: HTMLElement): boolean;\n skipFonts?: boolean;\n /** Bust the image cache so a CORS fetch is fresh. */\n cacheBust?: boolean;\n /** Data URL drawn instead of an image that fails to load. */\n imagePlaceholder?: string;\n onImageErrorHandler?(...args: unknown[]): void;\n};\n\n/**\n * Render a live element to a canvas through the SVG foreignObject renderer (html-to-image). It keeps\n * the browser's own layout and fonts where html2canvas re-implements them, and it takes a `filter`\n * and a root `style` override — the right tool for a transformed or panned surface. Loads on first use.\n */\nexport async function renderElement(element: HTMLElement, options: RenderOptions = {}): Promise<HTMLCanvasElement> {\n const { toCanvas } = await import(\"html-to-image\");\n return toCanvas(element, options as Parameters<typeof toCanvas>[1]);\n}\n\n/** `renderElement` answering a PNG data URL (what a chat attachment preview needs besides the Blob). */\nexport async function renderElementToDataUrl(element: HTMLElement, options: RenderOptions = {}): Promise<string> {\n const { toPng } = await import(\"html-to-image\");\n return toPng(element, options as Parameters<typeof toPng>[1]);\n}\n\nexport type PdfMargins = { top: number; right: number; bottom: number; left: number };\nexport type PdfPage = {\n image: HTMLCanvasElement;\n /** \"JPEG\" (default 0.92 quality) keeps a flat chart an order of magnitude smaller than a PNG embed. */\n imageType?: \"PNG\" | \"JPEG\";\n quality?: number;\n compression?: \"NONE\" | \"FAST\" | \"MEDIUM\" | \"SLOW\";\n /** \"fill\": the image covers the page. Margins: the image is scaled to fit inside them, never cropped. */\n placement?: \"fill\" | PdfMargins;\n /** A line of text drawn on the page (e.g. the as-of date a chart is true of). */\n header?: { text: string; x: number; y: number; fontSize?: number };\n};\nexport type PdfDocumentOptions = {\n orientation?: \"portrait\" | \"landscape\";\n unit?: \"pt\" | \"mm\" | \"cm\" | \"in\" | \"px\";\n format?: string | [number, number];\n};\n\n/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob. */\nexport async function pagesToPdf(pages: readonly PdfPage[], options: PdfDocumentOptions = {}): Promise<Blob> {\n const { jsPDF } = await import(\"jspdf\");\n const doc = new jsPDF({ orientation: options.orientation ?? \"portrait\", unit: options.unit ?? \"pt\", format: options.format ?? \"a4\" });\n pages.forEach((page, index) => {\n if (index > 0) doc.addPage();\n const pageW = doc.internal.pageSize.getWidth();\n const pageH = doc.internal.pageSize.getHeight();\n if (page.header) {\n doc.setFontSize(page.header.fontSize ?? 12);\n doc.text(page.header.text, page.header.x, page.header.y);\n }\n const type = page.imageType ?? \"PNG\";\n const data = type === \"JPEG\" ? page.image.toDataURL(\"image/jpeg\", page.quality ?? 0.92) : page.image.toDataURL(\"image/png\");\n const placement = page.placement ?? \"fill\";\n let x = 0, y = 0, w = pageW, h = pageH;\n if (placement !== \"fill\") {\n const availW = pageW - placement.left - placement.right;\n const availH = pageH - placement.top - placement.bottom;\n const scale = Math.min(availW / page.image.width, availH / page.image.height);\n x = placement.left;\n y = placement.top;\n w = page.image.width * scale;\n h = page.image.height * scale;\n }\n if (page.compression) doc.addImage(data, type, x, y, w, h, undefined, page.compression);\n else doc.addImage(data, type, x, y, w, h);\n });\n return doc.output(\"blob\");\n}\n\n/** Capture each element and lay them out one per PDF page. */\nexport async function elementsToPdf(\n elements: readonly HTMLElement[],\n options: PdfDocumentOptions & { capture?: CaptureOptions; page?: Omit<PdfPage, \"image\"> } = {},\n): Promise<Blob> {\n const pages: PdfPage[] = [];\n for (const element of elements) pages.push({ ...options.page, image: await captureElement(element, options.capture) });\n return pagesToPdf(pages, options);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/operate-capture.ts","../src/operate/capture.ts"],"sourcesContent":["/** `@ai-matrx/alchemy/operate/capture` — the one DOM capture engine (element → image / PDF). html2canvas, html-to-image and jsPDF load on first use. */\nexport {\n canvasToBlob,\n captureElement,\n elementToImage,\n elementsToPdf,\n pagesToPdf,\n renderElement,\n renderElementToDataUrl,\n type CaptureOptions,\n type PdfDocumentOptions,\n type PdfMargins,\n type PdfPage,\n type RenderOptions,\n} from \"./operate/capture\";\n","/**\n * `@ai-matrx/alchemy/operate/capture` — THE DOM capture door (ALC-19 T11): an element on screen →\n * an image or a PDF, with nothing else in the app importing html2canvas or jsPDF. The html2canvas +\n * jsPDF engine itself is `@ai-matrx/print/pdf` (`captureCanvas`, `pagesToPdf`), loaded on first use\n * (`import()`), so a page that never exports never downloads it; html-to-image (`renderElement`) is\n * this module's own.\n *\n * Tailwind 4 colours (oklch/lab/color()) are not parseable by html2canvas, which logs an error per\n * value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.\n */\n\n// THE one html2canvas + jsPDF engine is print's (AP-2 engine ruling: print sits below alchemy, so\n// alchemy loads it — never a second copy here). Type-only imports; the module loads on first use.\nimport type { CanvasCaptureOptions, PdfDocumentOptions, PdfPage } from \"@ai-matrx/print/pdf\";\nexport type { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins, PdfPage } from \"@ai-matrx/print/pdf\";\ntype CaptureOptions = CanvasCaptureOptions;\n\n/** Rasterise a live element to a canvas (what the person sees, at `scale`) — print's `captureCanvas`. */\nexport async function captureElement(element: HTMLElement, options: CaptureOptions = {}): Promise<HTMLCanvasElement> {\n const { captureCanvas } = await import(\"@ai-matrx/print/pdf\");\n return captureCanvas(element, options);\n}\n\n/** Encode a canvas (PNG by default; JPEG/WebP take a 0–1 quality). Throws when the browser refuses. */\nexport async function canvasToBlob(canvas: HTMLCanvasElement, type: \"image/png\" | \"image/jpeg\" | \"image/webp\" = \"image/png\", quality?: number): Promise<Blob> {\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (!blob) throw new Error(\"The image could not be encoded.\");\n return blob;\n}\n\n/** Capture an element straight to an image Blob. */\nexport async function elementToImage(\n element: HTMLElement,\n options: CaptureOptions & { type?: \"image/png\" | \"image/jpeg\" | \"image/webp\"; quality?: number } = {},\n): Promise<Blob> {\n const { type, quality, ...capture } = options;\n return canvasToBlob(await captureElement(element, capture), type, quality);\n}\n\n/** Options for `renderElement` (html-to-image): the SVG foreignObject renderer. */\nexport type RenderOptions = {\n /** Device-pixel ratio (default 1). */\n pixelRatio?: number;\n width?: number;\n height?: number;\n /** Final canvas size; the render is scaled to it. */\n canvasWidth?: number;\n canvasHeight?: number;\n backgroundColor?: string;\n /** Inline style applied to the cloned root (e.g. a translate to pad the chart). */\n style?: Partial<CSSStyleDeclaration>;\n /** Return false to leave a node out of the picture. */\n filter?(node: HTMLElement): boolean;\n skipFonts?: boolean;\n /** Bust the image cache so a CORS fetch is fresh. */\n cacheBust?: boolean;\n /** Data URL drawn instead of an image that fails to load. */\n imagePlaceholder?: string;\n onImageErrorHandler?(...args: unknown[]): void;\n};\n\n/**\n * Render a live element to a canvas through the SVG foreignObject renderer (html-to-image). It keeps\n * the browser's own layout and fonts where html2canvas re-implements them, and it takes a `filter`\n * and a root `style` override — the right tool for a transformed or panned surface. Loads on first use.\n */\nexport async function renderElement(element: HTMLElement, options: RenderOptions = {}): Promise<HTMLCanvasElement> {\n const { toCanvas } = await import(\"html-to-image\");\n return toCanvas(element, options as Parameters<typeof toCanvas>[1]);\n}\n\n/** `renderElement` answering a PNG data URL (what a chat attachment preview needs besides the Blob). */\nexport async function renderElementToDataUrl(element: HTMLElement, options: RenderOptions = {}): Promise<string> {\n const { toPng } = await import(\"html-to-image\");\n return toPng(element, options as Parameters<typeof toPng>[1]);\n}\n\n/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob — print's `pagesToPdf`. */\nexport async function pagesToPdf(pages: readonly PdfPage[], options: PdfDocumentOptions = {}): Promise<Blob> {\n const { pagesToPdf: layOut } = await import(\"@ai-matrx/print/pdf\");\n return layOut(pages, options);\n}\n\n/** Capture each element and lay them out one per PDF page. */\nexport async function elementsToPdf(\n elements: readonly HTMLElement[],\n options: PdfDocumentOptions & { capture?: CaptureOptions; page?: Omit<PdfPage, \"image\"> } = {},\n): Promise<Blob> {\n const pages: PdfPage[] = [];\n for (const element of elements) pages.push({ ...options.page, image: await captureElement(element, options.capture) });\n return pagesToPdf(pages, options);\n}\n"],"mappings":"y5BAAA,sWCkBA,eAAsB,eAAe,QAAsB,QAA0B,CAAC,EAA+B,CACnH,GAAM,CAAE,aAAc,EAAI,KAAM,QAAO,qBAAqB,EAC5D,OAAO,cAAc,QAAS,OAAO,CACvC,CAGA,eAAsB,aAAa,OAA2B,KAAkD,YAAa,QAAiC,CAC5J,IAAM,KAAO,MAAM,IAAI,QAAsB,SAAY,OAAO,OAAO,QAAS,KAAM,OAAO,CAAC,EAC9F,GAAI,CAAC,KAAM,MAAM,IAAI,MAAM,iCAAiC,EAC5D,OAAO,IACT,CAGA,eAAsB,eACpB,QACA,QAAmG,CAAC,EACrF,CACf,GAAM,CAAE,KAAM,QAAS,GAAG,OAAQ,EAAI,QACtC,OAAO,aAAa,MAAM,eAAe,QAAS,OAAO,EAAG,KAAM,OAAO,CAC3E,CA6BA,eAAsB,cAAc,QAAsB,QAAyB,CAAC,EAA+B,CACjH,GAAM,CAAE,QAAS,EAAI,KAAM,QAAO,eAAe,EACjD,OAAO,SAAS,QAAS,OAAyC,CACpE,CAGA,eAAsB,uBAAuB,QAAsB,QAAyB,CAAC,EAAoB,CAC/G,GAAM,CAAE,KAAM,EAAI,KAAM,QAAO,eAAe,EAC9C,OAAO,MAAM,QAAS,OAAsC,CAC9D,CAGA,eAAsB,WAAW,MAA2B,QAA8B,CAAC,EAAkB,CAC3G,GAAM,CAAE,WAAY,MAAO,EAAI,KAAM,QAAO,qBAAqB,EACjE,OAAO,OAAO,MAAO,OAAO,CAC9B,CAGA,eAAsB,cACpB,SACA,QAA4F,CAAC,EAC9E,CACf,IAAM,MAAmB,CAAC,EAC1B,QAAW,WAAW,SAAU,MAAM,KAAK,CAAE,GAAG,QAAQ,KAAM,MAAO,MAAM,eAAe,QAAS,QAAQ,OAAO,CAAE,CAAC,EACrH,OAAO,WAAW,MAAO,OAAO,CAClC","names":[]}
|
|
@@ -1,35 +1,19 @@
|
|
|
1
|
+
import { CanvasCaptureOptions, PdfDocumentOptions, PdfPage } from '@ai-matrx/print/pdf';
|
|
2
|
+
export { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins, PdfPage } from '@ai-matrx/print/pdf';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
|
-
* `@ai-matrx/alchemy/operate/capture` — THE DOM capture
|
|
3
|
-
* an image or a PDF, with nothing else in the app importing html2canvas or jsPDF.
|
|
4
|
-
*
|
|
5
|
+
* `@ai-matrx/alchemy/operate/capture` — THE DOM capture door (ALC-19 T11): an element on screen →
|
|
6
|
+
* an image or a PDF, with nothing else in the app importing html2canvas or jsPDF. The html2canvas +
|
|
7
|
+
* jsPDF engine itself is `@ai-matrx/print/pdf` (`captureCanvas`, `pagesToPdf`), loaded on first use
|
|
8
|
+
* (`import()`), so a page that never exports never downloads it; html-to-image (`renderElement`) is
|
|
9
|
+
* this module's own.
|
|
5
10
|
*
|
|
6
11
|
* Tailwind 4 colours (oklch/lab/color()) are not parseable by html2canvas, which logs an error per
|
|
7
12
|
* value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.
|
|
8
13
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/** Canvas background; null keeps transparency. */
|
|
13
|
-
backgroundColor?: string | null;
|
|
14
|
-
useCORS?: boolean;
|
|
15
|
-
allowTaint?: boolean;
|
|
16
|
-
logging?: boolean;
|
|
17
|
-
width?: number;
|
|
18
|
-
height?: number;
|
|
19
|
-
x?: number;
|
|
20
|
-
y?: number;
|
|
21
|
-
scrollX?: number;
|
|
22
|
-
scrollY?: number;
|
|
23
|
-
windowWidth?: number;
|
|
24
|
-
windowHeight?: number;
|
|
25
|
-
ignoreElements?(element: Element): boolean;
|
|
26
|
-
onclone?(document: Document, element: HTMLElement): void | Promise<void>;
|
|
27
|
-
/** Replace unparseable modern colour functions while capturing (`{ dark }` picks the palette). */
|
|
28
|
-
safeColors?: boolean | {
|
|
29
|
-
dark: boolean;
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
/** Rasterise a live element to a canvas (what the person sees, at `scale`). */
|
|
14
|
+
|
|
15
|
+
type CaptureOptions = CanvasCaptureOptions;
|
|
16
|
+
/** Rasterise a live element to a canvas (what the person sees, at `scale`) — print's `captureCanvas`. */
|
|
33
17
|
declare function captureElement(element: HTMLElement, options?: CaptureOptions): Promise<HTMLCanvasElement>;
|
|
34
18
|
/** Encode a canvas (PNG by default; JPEG/WebP take a 0–1 quality). Throws when the browser refuses. */
|
|
35
19
|
declare function canvasToBlob(canvas: HTMLCanvasElement, type?: "image/png" | "image/jpeg" | "image/webp", quality?: number): Promise<Blob>;
|
|
@@ -67,34 +51,7 @@ type RenderOptions = {
|
|
|
67
51
|
declare function renderElement(element: HTMLElement, options?: RenderOptions): Promise<HTMLCanvasElement>;
|
|
68
52
|
/** `renderElement` answering a PNG data URL (what a chat attachment preview needs besides the Blob). */
|
|
69
53
|
declare function renderElementToDataUrl(element: HTMLElement, options?: RenderOptions): Promise<string>;
|
|
70
|
-
|
|
71
|
-
top: number;
|
|
72
|
-
right: number;
|
|
73
|
-
bottom: number;
|
|
74
|
-
left: number;
|
|
75
|
-
};
|
|
76
|
-
type PdfPage = {
|
|
77
|
-
image: HTMLCanvasElement;
|
|
78
|
-
/** "JPEG" (default 0.92 quality) keeps a flat chart an order of magnitude smaller than a PNG embed. */
|
|
79
|
-
imageType?: "PNG" | "JPEG";
|
|
80
|
-
quality?: number;
|
|
81
|
-
compression?: "NONE" | "FAST" | "MEDIUM" | "SLOW";
|
|
82
|
-
/** "fill": the image covers the page. Margins: the image is scaled to fit inside them, never cropped. */
|
|
83
|
-
placement?: "fill" | PdfMargins;
|
|
84
|
-
/** A line of text drawn on the page (e.g. the as-of date a chart is true of). */
|
|
85
|
-
header?: {
|
|
86
|
-
text: string;
|
|
87
|
-
x: number;
|
|
88
|
-
y: number;
|
|
89
|
-
fontSize?: number;
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
type PdfDocumentOptions = {
|
|
93
|
-
orientation?: "portrait" | "landscape";
|
|
94
|
-
unit?: "pt" | "mm" | "cm" | "in" | "px";
|
|
95
|
-
format?: string | [number, number];
|
|
96
|
-
};
|
|
97
|
-
/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob. */
|
|
54
|
+
/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob — print's `pagesToPdf`. */
|
|
98
55
|
declare function pagesToPdf(pages: readonly PdfPage[], options?: PdfDocumentOptions): Promise<Blob>;
|
|
99
56
|
/** Capture each element and lay them out one per PDF page. */
|
|
100
57
|
declare function elementsToPdf(elements: readonly HTMLElement[], options?: PdfDocumentOptions & {
|
|
@@ -102,4 +59,4 @@ declare function elementsToPdf(elements: readonly HTMLElement[], options?: PdfDo
|
|
|
102
59
|
page?: Omit<PdfPage, "image">;
|
|
103
60
|
}): Promise<Blob>;
|
|
104
61
|
|
|
105
|
-
export { type
|
|
62
|
+
export { type RenderOptions, canvasToBlob, captureElement, elementToImage, elementsToPdf, pagesToPdf, renderElement, renderElementToDataUrl };
|
|
@@ -1,35 +1,19 @@
|
|
|
1
|
+
import { CanvasCaptureOptions, PdfDocumentOptions, PdfPage } from '@ai-matrx/print/pdf';
|
|
2
|
+
export { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins, PdfPage } from '@ai-matrx/print/pdf';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
|
-
* `@ai-matrx/alchemy/operate/capture` — THE DOM capture
|
|
3
|
-
* an image or a PDF, with nothing else in the app importing html2canvas or jsPDF.
|
|
4
|
-
*
|
|
5
|
+
* `@ai-matrx/alchemy/operate/capture` — THE DOM capture door (ALC-19 T11): an element on screen →
|
|
6
|
+
* an image or a PDF, with nothing else in the app importing html2canvas or jsPDF. The html2canvas +
|
|
7
|
+
* jsPDF engine itself is `@ai-matrx/print/pdf` (`captureCanvas`, `pagesToPdf`), loaded on first use
|
|
8
|
+
* (`import()`), so a page that never exports never downloads it; html-to-image (`renderElement`) is
|
|
9
|
+
* this module's own.
|
|
5
10
|
*
|
|
6
11
|
* Tailwind 4 colours (oklch/lab/color()) are not parseable by html2canvas, which logs an error per
|
|
7
12
|
* value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.
|
|
8
13
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/** Canvas background; null keeps transparency. */
|
|
13
|
-
backgroundColor?: string | null;
|
|
14
|
-
useCORS?: boolean;
|
|
15
|
-
allowTaint?: boolean;
|
|
16
|
-
logging?: boolean;
|
|
17
|
-
width?: number;
|
|
18
|
-
height?: number;
|
|
19
|
-
x?: number;
|
|
20
|
-
y?: number;
|
|
21
|
-
scrollX?: number;
|
|
22
|
-
scrollY?: number;
|
|
23
|
-
windowWidth?: number;
|
|
24
|
-
windowHeight?: number;
|
|
25
|
-
ignoreElements?(element: Element): boolean;
|
|
26
|
-
onclone?(document: Document, element: HTMLElement): void | Promise<void>;
|
|
27
|
-
/** Replace unparseable modern colour functions while capturing (`{ dark }` picks the palette). */
|
|
28
|
-
safeColors?: boolean | {
|
|
29
|
-
dark: boolean;
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
/** Rasterise a live element to a canvas (what the person sees, at `scale`). */
|
|
14
|
+
|
|
15
|
+
type CaptureOptions = CanvasCaptureOptions;
|
|
16
|
+
/** Rasterise a live element to a canvas (what the person sees, at `scale`) — print's `captureCanvas`. */
|
|
33
17
|
declare function captureElement(element: HTMLElement, options?: CaptureOptions): Promise<HTMLCanvasElement>;
|
|
34
18
|
/** Encode a canvas (PNG by default; JPEG/WebP take a 0–1 quality). Throws when the browser refuses. */
|
|
35
19
|
declare function canvasToBlob(canvas: HTMLCanvasElement, type?: "image/png" | "image/jpeg" | "image/webp", quality?: number): Promise<Blob>;
|
|
@@ -67,34 +51,7 @@ type RenderOptions = {
|
|
|
67
51
|
declare function renderElement(element: HTMLElement, options?: RenderOptions): Promise<HTMLCanvasElement>;
|
|
68
52
|
/** `renderElement` answering a PNG data URL (what a chat attachment preview needs besides the Blob). */
|
|
69
53
|
declare function renderElementToDataUrl(element: HTMLElement, options?: RenderOptions): Promise<string>;
|
|
70
|
-
|
|
71
|
-
top: number;
|
|
72
|
-
right: number;
|
|
73
|
-
bottom: number;
|
|
74
|
-
left: number;
|
|
75
|
-
};
|
|
76
|
-
type PdfPage = {
|
|
77
|
-
image: HTMLCanvasElement;
|
|
78
|
-
/** "JPEG" (default 0.92 quality) keeps a flat chart an order of magnitude smaller than a PNG embed. */
|
|
79
|
-
imageType?: "PNG" | "JPEG";
|
|
80
|
-
quality?: number;
|
|
81
|
-
compression?: "NONE" | "FAST" | "MEDIUM" | "SLOW";
|
|
82
|
-
/** "fill": the image covers the page. Margins: the image is scaled to fit inside them, never cropped. */
|
|
83
|
-
placement?: "fill" | PdfMargins;
|
|
84
|
-
/** A line of text drawn on the page (e.g. the as-of date a chart is true of). */
|
|
85
|
-
header?: {
|
|
86
|
-
text: string;
|
|
87
|
-
x: number;
|
|
88
|
-
y: number;
|
|
89
|
-
fontSize?: number;
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
type PdfDocumentOptions = {
|
|
93
|
-
orientation?: "portrait" | "landscape";
|
|
94
|
-
unit?: "pt" | "mm" | "cm" | "in" | "px";
|
|
95
|
-
format?: string | [number, number];
|
|
96
|
-
};
|
|
97
|
-
/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob. */
|
|
54
|
+
/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob — print's `pagesToPdf`. */
|
|
98
55
|
declare function pagesToPdf(pages: readonly PdfPage[], options?: PdfDocumentOptions): Promise<Blob>;
|
|
99
56
|
/** Capture each element and lay them out one per PDF page. */
|
|
100
57
|
declare function elementsToPdf(elements: readonly HTMLElement[], options?: PdfDocumentOptions & {
|
|
@@ -102,4 +59,4 @@ declare function elementsToPdf(elements: readonly HTMLElement[], options?: PdfDo
|
|
|
102
59
|
page?: Omit<PdfPage, "image">;
|
|
103
60
|
}): Promise<Blob>;
|
|
104
61
|
|
|
105
|
-
export { type
|
|
62
|
+
export { type RenderOptions, canvasToBlob, captureElement, elementToImage, elementsToPdf, pagesToPdf, renderElement, renderElementToDataUrl };
|
package/dist/operate-capture.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
async function captureElement(element,options={}){let{captureCanvas}=await import("@ai-matrx/print/pdf");return captureCanvas(element,options)}async function canvasToBlob(canvas,type="image/png",quality){let blob=await new Promise(resolve=>canvas.toBlob(resolve,type,quality));if(!blob)throw new Error("The image could not be encoded.");return blob}async function elementToImage(element,options={}){let{type,quality,...capture}=options;return canvasToBlob(await captureElement(element,capture),type,quality)}async function renderElement(element,options={}){let{toCanvas}=await import("html-to-image");return toCanvas(element,options)}async function renderElementToDataUrl(element,options={}){let{toPng}=await import("html-to-image");return toPng(element,options)}async function pagesToPdf(pages,options={}){let{pagesToPdf:layOut}=await import("@ai-matrx/print/pdf");return layOut(pages,options)}async function elementsToPdf(elements,options={}){let pages=[];for(let element of elements)pages.push({...options.page,image:await captureElement(element,options.capture)});return pagesToPdf(pages,options)}export{canvasToBlob,captureElement,elementToImage,elementsToPdf,pagesToPdf,renderElement,renderElementToDataUrl};
|
|
2
2
|
//# sourceMappingURL=operate-capture.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/operate/capture.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/operate/capture` — THE DOM capture engine (ALC-19 T11): an element on screen →\n * an image or a PDF, with nothing else in the app importing html2canvas or jsPDF. Both libraries\n * load on first use (`import()`), so a page that never exports never downloads them.\n *\n * Tailwind 4 colours (oklch/lab/color()) are not parseable by html2canvas, which logs an error per\n * value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.\n */\n\nexport type CaptureOptions = {\n /** Device-pixel scale of the capture (default 2 — legible when scaled in a deck or print). */\n scale?: number;\n /** Canvas background; null keeps transparency. */\n backgroundColor?: string | null;\n useCORS?: boolean;\n allowTaint?: boolean;\n logging?: boolean;\n width?: number;\n height?: number;\n x?: number;\n y?: number;\n scrollX?: number;\n scrollY?: number;\n windowWidth?: number;\n windowHeight?: number;\n ignoreElements?(element: Element): boolean;\n onclone?(document: Document, element: HTMLElement): void | Promise<void>;\n /** Replace unparseable modern colour functions while capturing (`{ dark }` picks the palette). */\n safeColors?: boolean | { dark: boolean };\n};\n\nconst UNSAFE_COLOR = /\\b(oklch|oklab|lab|lch|color)\\s*\\(/i;\n\nfunction safeColor(value: string, prop: string, dark: boolean): string {\n if (!UNSAFE_COLOR.test(value)) return value;\n const p = prop.toLowerCase();\n if (p.includes(\"background\")) return dark ? \"#1f2937\" : \"#ffffff\";\n if (p === \"color\") return dark ? \"#f3f4f6\" : \"#111827\";\n if (p.includes(\"border\") || p.includes(\"outline\")) return dark ? \"#4b5563\" : \"#d1d5db\";\n if (p === \"fill\" || p === \"stroke\") return dark ? \"#9ca3af\" : \"#374151\";\n return \"#6b7280\";\n}\n\n/** Patch `getComputedStyle` so the capture never reads an unsafe colour; returns the restore. */\nfunction patchComputedStyle(dark: boolean): () => void {\n const original = window.getComputedStyle;\n const bound = original.bind(window);\n window.getComputedStyle = function (elt: Element, pseudo?: string | null) {\n const computed = bound(elt, pseudo);\n return new Proxy(computed, {\n get(target, prop: string | symbol) {\n const value = (target as unknown as Record<string | symbol, unknown>)[prop];\n if (typeof prop === \"string\" && typeof value === \"string\" && value && UNSAFE_COLOR.test(value)) return safeColor(value, prop, dark);\n if (prop === \"getPropertyValue\")\n return (p: string) => {\n const raw = target.getPropertyValue(p);\n return raw && UNSAFE_COLOR.test(raw) ? safeColor(raw, p, dark) : raw;\n };\n if (typeof value === \"function\") return (value as CallableFunction).bind(target);\n return value;\n },\n });\n } as typeof window.getComputedStyle;\n return () => {\n window.getComputedStyle = original;\n };\n}\n\nconst prefersDark = () =>\n typeof document !== \"undefined\" &&\n (document.documentElement.classList.contains(\"dark\") || (typeof window.matchMedia === \"function\" && window.matchMedia(\"(prefers-color-scheme: dark)\").matches));\n\n/** Rasterise a live element to a canvas (what the person sees, at `scale`). */\nexport async function captureElement(element: HTMLElement, options: CaptureOptions = {}): Promise<HTMLCanvasElement> {\n const { safeColors, ...rest } = options;\n const { default: html2canvas } = await import(\"html2canvas\");\n const restore = safeColors ? patchComputedStyle(typeof safeColors === \"object\" ? safeColors.dark : prefersDark()) : () => {};\n try {\n return await html2canvas(element, { scale: 2, logging: false, useCORS: true, ...rest } as Parameters<typeof html2canvas>[1]);\n } finally {\n restore();\n }\n}\n\n/** Encode a canvas (PNG by default; JPEG/WebP take a 0–1 quality). Throws when the browser refuses. */\nexport async function canvasToBlob(canvas: HTMLCanvasElement, type: \"image/png\" | \"image/jpeg\" | \"image/webp\" = \"image/png\", quality?: number): Promise<Blob> {\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (!blob) throw new Error(\"The image could not be encoded.\");\n return blob;\n}\n\n/** Capture an element straight to an image Blob. */\nexport async function elementToImage(\n element: HTMLElement,\n options: CaptureOptions & { type?: \"image/png\" | \"image/jpeg\" | \"image/webp\"; quality?: number } = {},\n): Promise<Blob> {\n const { type, quality, ...capture } = options;\n return canvasToBlob(await captureElement(element, capture), type, quality);\n}\n\n/** Options for `renderElement` (html-to-image): the SVG foreignObject renderer. */\nexport type RenderOptions = {\n /** Device-pixel ratio (default 1). */\n pixelRatio?: number;\n width?: number;\n height?: number;\n /** Final canvas size; the render is scaled to it. */\n canvasWidth?: number;\n canvasHeight?: number;\n backgroundColor?: string;\n /** Inline style applied to the cloned root (e.g. a translate to pad the chart). */\n style?: Partial<CSSStyleDeclaration>;\n /** Return false to leave a node out of the picture. */\n filter?(node: HTMLElement): boolean;\n skipFonts?: boolean;\n /** Bust the image cache so a CORS fetch is fresh. */\n cacheBust?: boolean;\n /** Data URL drawn instead of an image that fails to load. */\n imagePlaceholder?: string;\n onImageErrorHandler?(...args: unknown[]): void;\n};\n\n/**\n * Render a live element to a canvas through the SVG foreignObject renderer (html-to-image). It keeps\n * the browser's own layout and fonts where html2canvas re-implements them, and it takes a `filter`\n * and a root `style` override — the right tool for a transformed or panned surface. Loads on first use.\n */\nexport async function renderElement(element: HTMLElement, options: RenderOptions = {}): Promise<HTMLCanvasElement> {\n const { toCanvas } = await import(\"html-to-image\");\n return toCanvas(element, options as Parameters<typeof toCanvas>[1]);\n}\n\n/** `renderElement` answering a PNG data URL (what a chat attachment preview needs besides the Blob). */\nexport async function renderElementToDataUrl(element: HTMLElement, options: RenderOptions = {}): Promise<string> {\n const { toPng } = await import(\"html-to-image\");\n return toPng(element, options as Parameters<typeof toPng>[1]);\n}\n\nexport type PdfMargins = { top: number; right: number; bottom: number; left: number };\nexport type PdfPage = {\n image: HTMLCanvasElement;\n /** \"JPEG\" (default 0.92 quality) keeps a flat chart an order of magnitude smaller than a PNG embed. */\n imageType?: \"PNG\" | \"JPEG\";\n quality?: number;\n compression?: \"NONE\" | \"FAST\" | \"MEDIUM\" | \"SLOW\";\n /** \"fill\": the image covers the page. Margins: the image is scaled to fit inside them, never cropped. */\n placement?: \"fill\" | PdfMargins;\n /** A line of text drawn on the page (e.g. the as-of date a chart is true of). */\n header?: { text: string; x: number; y: number; fontSize?: number };\n};\nexport type PdfDocumentOptions = {\n orientation?: \"portrait\" | \"landscape\";\n unit?: \"pt\" | \"mm\" | \"cm\" | \"in\" | \"px\";\n format?: string | [number, number];\n};\n\n/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob. */\nexport async function pagesToPdf(pages: readonly PdfPage[], options: PdfDocumentOptions = {}): Promise<Blob> {\n const { jsPDF } = await import(\"jspdf\");\n const doc = new jsPDF({ orientation: options.orientation ?? \"portrait\", unit: options.unit ?? \"pt\", format: options.format ?? \"a4\" });\n pages.forEach((page, index) => {\n if (index > 0) doc.addPage();\n const pageW = doc.internal.pageSize.getWidth();\n const pageH = doc.internal.pageSize.getHeight();\n if (page.header) {\n doc.setFontSize(page.header.fontSize ?? 12);\n doc.text(page.header.text, page.header.x, page.header.y);\n }\n const type = page.imageType ?? \"PNG\";\n const data = type === \"JPEG\" ? page.image.toDataURL(\"image/jpeg\", page.quality ?? 0.92) : page.image.toDataURL(\"image/png\");\n const placement = page.placement ?? \"fill\";\n let x = 0, y = 0, w = pageW, h = pageH;\n if (placement !== \"fill\") {\n const availW = pageW - placement.left - placement.right;\n const availH = pageH - placement.top - placement.bottom;\n const scale = Math.min(availW / page.image.width, availH / page.image.height);\n x = placement.left;\n y = placement.top;\n w = page.image.width * scale;\n h = page.image.height * scale;\n }\n if (page.compression) doc.addImage(data, type, x, y, w, h, undefined, page.compression);\n else doc.addImage(data, type, x, y, w, h);\n });\n return doc.output(\"blob\");\n}\n\n/** Capture each element and lay them out one per PDF page. */\nexport async function elementsToPdf(\n elements: readonly HTMLElement[],\n options: PdfDocumentOptions & { capture?: CaptureOptions; page?: Omit<PdfPage, \"image\"> } = {},\n): Promise<Blob> {\n const pages: PdfPage[] = [];\n for (const element of elements) pages.push({ ...options.page, image: await captureElement(element, options.capture) });\n return pagesToPdf(pages, options);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/operate/capture.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/operate/capture` — THE DOM capture door (ALC-19 T11): an element on screen →\n * an image or a PDF, with nothing else in the app importing html2canvas or jsPDF. The html2canvas +\n * jsPDF engine itself is `@ai-matrx/print/pdf` (`captureCanvas`, `pagesToPdf`), loaded on first use\n * (`import()`), so a page that never exports never downloads it; html-to-image (`renderElement`) is\n * this module's own.\n *\n * Tailwind 4 colours (oklch/lab/color()) are not parseable by html2canvas, which logs an error per\n * value; `safeColors` substitutes theme-appropriate hex values for the duration of the capture.\n */\n\n// THE one html2canvas + jsPDF engine is print's (AP-2 engine ruling: print sits below alchemy, so\n// alchemy loads it — never a second copy here). Type-only imports; the module loads on first use.\nimport type { CanvasCaptureOptions, PdfDocumentOptions, PdfPage } from \"@ai-matrx/print/pdf\";\nexport type { CanvasCaptureOptions as CaptureOptions, PdfDocumentOptions, PdfMargins, PdfPage } from \"@ai-matrx/print/pdf\";\ntype CaptureOptions = CanvasCaptureOptions;\n\n/** Rasterise a live element to a canvas (what the person sees, at `scale`) — print's `captureCanvas`. */\nexport async function captureElement(element: HTMLElement, options: CaptureOptions = {}): Promise<HTMLCanvasElement> {\n const { captureCanvas } = await import(\"@ai-matrx/print/pdf\");\n return captureCanvas(element, options);\n}\n\n/** Encode a canvas (PNG by default; JPEG/WebP take a 0–1 quality). Throws when the browser refuses. */\nexport async function canvasToBlob(canvas: HTMLCanvasElement, type: \"image/png\" | \"image/jpeg\" | \"image/webp\" = \"image/png\", quality?: number): Promise<Blob> {\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (!blob) throw new Error(\"The image could not be encoded.\");\n return blob;\n}\n\n/** Capture an element straight to an image Blob. */\nexport async function elementToImage(\n element: HTMLElement,\n options: CaptureOptions & { type?: \"image/png\" | \"image/jpeg\" | \"image/webp\"; quality?: number } = {},\n): Promise<Blob> {\n const { type, quality, ...capture } = options;\n return canvasToBlob(await captureElement(element, capture), type, quality);\n}\n\n/** Options for `renderElement` (html-to-image): the SVG foreignObject renderer. */\nexport type RenderOptions = {\n /** Device-pixel ratio (default 1). */\n pixelRatio?: number;\n width?: number;\n height?: number;\n /** Final canvas size; the render is scaled to it. */\n canvasWidth?: number;\n canvasHeight?: number;\n backgroundColor?: string;\n /** Inline style applied to the cloned root (e.g. a translate to pad the chart). */\n style?: Partial<CSSStyleDeclaration>;\n /** Return false to leave a node out of the picture. */\n filter?(node: HTMLElement): boolean;\n skipFonts?: boolean;\n /** Bust the image cache so a CORS fetch is fresh. */\n cacheBust?: boolean;\n /** Data URL drawn instead of an image that fails to load. */\n imagePlaceholder?: string;\n onImageErrorHandler?(...args: unknown[]): void;\n};\n\n/**\n * Render a live element to a canvas through the SVG foreignObject renderer (html-to-image). It keeps\n * the browser's own layout and fonts where html2canvas re-implements them, and it takes a `filter`\n * and a root `style` override — the right tool for a transformed or panned surface. Loads on first use.\n */\nexport async function renderElement(element: HTMLElement, options: RenderOptions = {}): Promise<HTMLCanvasElement> {\n const { toCanvas } = await import(\"html-to-image\");\n return toCanvas(element, options as Parameters<typeof toCanvas>[1]);\n}\n\n/** `renderElement` answering a PNG data URL (what a chat attachment preview needs besides the Blob). */\nexport async function renderElementToDataUrl(element: HTMLElement, options: RenderOptions = {}): Promise<string> {\n const { toPng } = await import(\"html-to-image\");\n return toPng(element, options as Parameters<typeof toPng>[1]);\n}\n\n/** Lay canvases out as PDF pages (one page each) and answer the PDF Blob — print's `pagesToPdf`. */\nexport async function pagesToPdf(pages: readonly PdfPage[], options: PdfDocumentOptions = {}): Promise<Blob> {\n const { pagesToPdf: layOut } = await import(\"@ai-matrx/print/pdf\");\n return layOut(pages, options);\n}\n\n/** Capture each element and lay them out one per PDF page. */\nexport async function elementsToPdf(\n elements: readonly HTMLElement[],\n options: PdfDocumentOptions & { capture?: CaptureOptions; page?: Omit<PdfPage, \"image\"> } = {},\n): Promise<Blob> {\n const pages: PdfPage[] = [];\n for (const element of elements) pages.push({ ...options.page, image: await captureElement(element, options.capture) });\n return pagesToPdf(pages, options);\n}\n"],"mappings":"AAkBA,eAAsB,eAAe,QAAsB,QAA0B,CAAC,EAA+B,CACnH,GAAM,CAAE,aAAc,EAAI,KAAM,QAAO,qBAAqB,EAC5D,OAAO,cAAc,QAAS,OAAO,CACvC,CAGA,eAAsB,aAAa,OAA2B,KAAkD,YAAa,QAAiC,CAC5J,IAAM,KAAO,MAAM,IAAI,QAAsB,SAAY,OAAO,OAAO,QAAS,KAAM,OAAO,CAAC,EAC9F,GAAI,CAAC,KAAM,MAAM,IAAI,MAAM,iCAAiC,EAC5D,OAAO,IACT,CAGA,eAAsB,eACpB,QACA,QAAmG,CAAC,EACrF,CACf,GAAM,CAAE,KAAM,QAAS,GAAG,OAAQ,EAAI,QACtC,OAAO,aAAa,MAAM,eAAe,QAAS,OAAO,EAAG,KAAM,OAAO,CAC3E,CA6BA,eAAsB,cAAc,QAAsB,QAAyB,CAAC,EAA+B,CACjH,GAAM,CAAE,QAAS,EAAI,KAAM,QAAO,eAAe,EACjD,OAAO,SAAS,QAAS,OAAyC,CACpE,CAGA,eAAsB,uBAAuB,QAAsB,QAAyB,CAAC,EAAoB,CAC/G,GAAM,CAAE,KAAM,EAAI,KAAM,QAAO,eAAe,EAC9C,OAAO,MAAM,QAAS,OAAsC,CAC9D,CAGA,eAAsB,WAAW,MAA2B,QAA8B,CAAC,EAAkB,CAC3G,GAAM,CAAE,WAAY,MAAO,EAAI,KAAM,QAAO,qBAAqB,EACjE,OAAO,OAAO,MAAO,OAAO,CAC9B,CAGA,eAAsB,cACpB,SACA,QAA4F,CAAC,EAC9E,CACf,IAAM,MAAmB,CAAC,EAC1B,QAAW,WAAW,SAAU,MAAM,KAAK,CAAE,GAAG,QAAQ,KAAM,MAAO,MAAM,eAAe,QAAS,QAAQ,OAAO,CAAE,CAAC,EACrH,OAAO,WAAW,MAAO,OAAO,CAClC","names":[]}
|
|
@@ -64,7 +64,7 @@ type Notice = {
|
|
|
64
64
|
};
|
|
65
65
|
interface FormatOptions {
|
|
66
66
|
filename?: string;
|
|
67
|
-
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv
|
|
67
|
+
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv; an .xlsx's typed text cells are never evaluated, its plain text is the guarded TSV). */
|
|
68
68
|
spreadsheetSafe?: boolean;
|
|
69
69
|
/** Markdown→HTML skin: the two page looks plus "inline" for the rich clipboard (styles on elements). */
|
|
70
70
|
skin?: "document" | "article" | "inline";
|
|
@@ -64,7 +64,7 @@ type Notice = {
|
|
|
64
64
|
};
|
|
65
65
|
interface FormatOptions {
|
|
66
66
|
filename?: string;
|
|
67
|
-
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv
|
|
67
|
+
/** Formula guard for delimited text; unset = kit `spreadsheetSafeByDefault(format)` (on for csv/tsv; an .xlsx's typed text cells are never evaluated, its plain text is the guarded TSV). */
|
|
68
68
|
spreadsheetSafe?: boolean;
|
|
69
69
|
/** Markdown→HTML skin: the two page looks plus "inline" for the rich clipboard (styles on elements). */
|
|
70
70
|
skin?: "document" | "article" | "inline";
|
package/dist/operate-read.cjs
CHANGED
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var operate_read_exports={};__export(operate_read_exports,{UnreadableFormatError:()=>UnreadableFormatError,fileFormatOf:()=>fileFormatOf,parseDelimited:()=>parseDelimited,readFile:()=>readFile,readWorkbook:()=>readWorkbook,toDelimitedText:()=>toDelimitedText});module.exports=__toCommonJS(operate_read_exports);var import_operate=require("@ai-matrx/alchemy/operate"),
|
|
2
|
-
`),lf=text.indexOf(`
|
|
3
|
-
`),cr=text.indexOf("\r");return crlf!==-1&&crlf===cr?`\r
|
|
4
|
-
`:cr!==-1&&(lf===-1||cr<lf)?"\r":`
|
|
5
|
-
`}function tokenize(text,delimiter,linebreak,preview=1/0){let rows=[],errors=[];if(text==="")return{rows,errors};let row=[],i=0,n=text.length,dl=delimiter.length,ll=linebreak.length;for(;i<=n&&rows.length<preview;){if(text.startsWith('"',i)){let value="",j=i+1,closed=!1;for(;j<n;){let q=text.indexOf('"',j);if(q===-1)break;if(value+=text.slice(j,q),text[q+1]==='"'){value+='"',j=q+2;continue}let after=q+1;if(after>=n||text.startsWith(delimiter,after)||text.startsWith(linebreak,after)){closed=!0,j=after;break}errors.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:rows.length}),value+='"',j=after}if(!closed)return errors.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:rows.length}),value+=text.slice(j),row.push(value),rows.push(row),{rows,errors};if(row.push(value),i=j,i>=n){rows.push(row);break}if(text.startsWith(delimiter,i)){if(i+=dl,i>=n){row.push(""),rows.push(row);break}continue}if(rows.push(row),row=[],i+=ll,i>=n){rows.push([""]);break}continue}let nextDelim=text.indexOf(delimiter,i),nextBreak=text.indexOf(linebreak,i);if(nextDelim!==-1&&(nextBreak===-1||nextDelim<nextBreak)){if(row.push(text.slice(i,nextDelim)),i=nextDelim+dl,i>=n){row.push(""),rows.push(row);break}continue}if(nextBreak!==-1){if(row.push(text.slice(i,nextBreak)),rows.push(row),row=[],i=nextBreak+ll,i>=n){rows.push([""]);break}continue}row.push(text.slice(i)),rows.push(row);break}return{rows,errors}}var isEmptyRow=(row,mode)=>mode==="greedy"?row.join("").trim()==="":row.length===1&&row[0].length===0;function guessDelimiter(text,linebreak,skip){let best,bestDelta,bestAvg;for(let delim of GUESS_DELIMITERS){let{rows}=tokenize(text,delim,linebreak,10),delta=0,avg=0,empty=0,prev;for(let row of rows){if(skip&&isEmptyRow(row,skip)){empty++;continue}let count=row.length;if(avg+=count,prev===void 0){prev=count;continue}count>0&&(delta+=Math.abs(count-prev),prev=count)}rows.length>0&&(avg/=rows.length-empty),(bestDelta===void 0||delta<=bestDelta)&&(bestAvg===void 0||avg>bestAvg)&&avg>1.99&&(bestDelta=delta,bestAvg=avg,best=delim)}return{delimiter:best??",",successful:best!==void 0}}function typed(value){if(value==="true"||value==="TRUE")return!0;if(value==="false"||value==="FALSE")return!1;if(FLOAT.test(value)){let num=parseFloat(value);return num>-(2**53)&&num<2**53?num:value}return ISO_DATE.test(value)?new Date(value):value===""?null:value}function parseDelimited(text,options={}){let input=text.charCodeAt(0)===65279?text.slice(1):text,linebreak=guessLinebreak(input),errors=[],delimiter=options.delimiter;if(!delimiter){let guess=guessDelimiter(input,linebreak,options.skipEmptyLines);delimiter=guess.delimiter,guess.successful||errors.push({type:"Delimiter",code:"UndetectableDelimiter",message:"Unable to auto-detect delimiting character; defaulted to ','"})}let tokens=tokenize(input,delimiter,linebreak);errors.unshift(...tokens.errors);let rows=tokens.rows;options.skipEmptyLines&&(rows=rows.filter(row=>!isEmptyRow(row,options.skipEmptyLines)));let meta={delimiter,linebreak};if(!options.header)return{data:options.dynamicTyping?rows.map(row=>row.map(typed)):rows,errors,meta};let[head=[],...body]=rows,seen=new Map,fields=head.map((raw,index)=>{let name=options.transformHeader?options.transformHeader(raw,index):raw,count=seen.get(name)??0;return seen.set(name,count+1),count===0?name:`${name}_${count}`});return meta.fields=fields,{data:body.map((row,index)=>{let record={};return row.forEach((value,j)=>{let cell=options.dynamicTyping?typed(value):value;j>=fields.length?(record.__parsed_extra??=[]).push(cell):record[fields[j]]=cell}),row.length>fields.length?errors.push({type:"FieldMismatch",code:"TooManyFields",message:`Too many fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}):row.length<fields.length&&errors.push({type:"FieldMismatch",code:"TooFewFields",message:`Too few fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}),record}),errors,meta}}function toDelimitedText(header,rows,options={}){let columns=header.map((label,i)=>({id:`c${i}`,label,path:`/c${i}`,visible:!0})),record=row=>{let out={};return header.forEach((label,i)=>{let v=Array.isArray(row)?row[i]:row[label];v!==void 0&&(out[`c${i}`]=v)}),out},captured=(0,import_operate.directSource)({kind:"rows",columns,rows:rows.map(record)}),draft=(0,import_operate.createDraft)((0,import_operate.projectSnapshot)(captured.snapshot,captured.projection));return(0,import_operate.serialize)(draft,options.format??"csv",options.spreadsheetSafe===void 0?{}:{spreadsheetSafe:options.spreadsheetSafe}).plainText}async function bytesOf(input){return typeof input=="string"?new TextEncoder().encode(input):input instanceof Uint8Array?input:input instanceof ArrayBuffer?new Uint8Array(input):new Uint8Array(await input.arrayBuffer())}async function readWorkbook(input,options={}){let bytes=await bytesOf(input),{readWorkbookBytes}=await import("@ai-matrx/alchemy/operate/formats/xlsx");return readWorkbookBytes(bytes,options)}var WORKBOOK_FORMATS=new Set(["xlsx","xls","ods","xlsm","xlsb"]),DELIMITED_FORMATS=new Set(["csv","tsv","txt","tab"]),UnreadableFormatError=class extends Error{format;constructor(format){super(`A "${format}" file is not a table we can read. Choose a CSV, TSV, or Excel file.`),this.format=format,this.name="UnreadableFormatError"}};function fileFormatOf(name){let m=/\.([a-z0-9]+)$/i.exec(name??"");return m?m[1].toLowerCase():""}async function readFile(input,options={}){let name=options.filename??(typeof Blob<"u"&&input instanceof Blob&&"name"in input?String(input.name):void 0),format=(options.format??fileFormatOf(name))||(typeof input=="string"?"csv":"");if(WORKBOOK_FORMATS.has(format)){let book=await readWorkbook(input,options.workbook);return{format:format==="xlsm"||format==="xlsb"?"xlsx":format,sheets:book.sheets.map(s=>({name:s.name,grid:s.grid(options.grid),merges:s.merges})),errors:[]}}if(!DELIMITED_FORMATS.has(format))throw new UnreadableFormatError(format||"unknown");let text=typeof input=="string"?input:new TextDecoder("utf-8").decode(await bytesOf(input)),parsed=parseDelimited(text,{...options,header:!1,...options.delimiter?{}:format==="tsv"||format==="tab"?{delimiter:" "}:{}});return{format:format==="tab"?"tsv":format,sheets:[{name:name?.replace(/\.[^.]+$/,"")||"Sheet1",grid:parsed.data,merges:[]}],errors:parsed.errors,delimiter:parsed.meta.delimiter}}
|
|
1
|
+
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var operate_read_exports={};__export(operate_read_exports,{UnreadableFormatError:()=>UnreadableFormatError,fileFormatOf:()=>fileFormatOf,parseDelimited:()=>import_delimited.parseDelimited,readFile:()=>readFile,readWorkbook:()=>readWorkbook,toDelimitedText:()=>toDelimitedText});module.exports=__toCommonJS(operate_read_exports);var import_operate=require("@ai-matrx/alchemy/operate"),import_delimited=require("@ai-matrx/kit/delimited"),import_delimited2=require("@ai-matrx/kit/delimited");function toDelimitedText(header,rows,options={}){let columns=header.map((label,i)=>({id:`c${i}`,label,path:`/c${i}`,visible:!0})),record=row=>{let out={};return header.forEach((label,i)=>{let v=Array.isArray(row)?row[i]:row[label];v!==void 0&&(out[`c${i}`]=v)}),out},captured=(0,import_operate.directSource)({kind:"rows",columns,rows:rows.map(record)}),draft=(0,import_operate.createDraft)((0,import_operate.projectSnapshot)(captured.snapshot,captured.projection));return(0,import_operate.serialize)(draft,options.format??"csv",options.spreadsheetSafe===void 0?{}:{spreadsheetSafe:options.spreadsheetSafe}).plainText}async function bytesOf(input){return typeof input=="string"?new TextEncoder().encode(input):input instanceof Uint8Array?input:input instanceof ArrayBuffer?new Uint8Array(input):new Uint8Array(await input.arrayBuffer())}async function readWorkbook(input,options={}){let bytes=await bytesOf(input),{readWorkbookBytes}=await import("@ai-matrx/alchemy/operate/formats/xlsx");return readWorkbookBytes(bytes,options)}var WORKBOOK_FORMATS=new Set(["xlsx","xls","ods","xlsm","xlsb"]),DELIMITED_FORMATS=new Set(["csv","tsv","txt","tab"]),UnreadableFormatError=class extends Error{format;constructor(format){super(`A "${format}" file is not a table we can read. Choose a CSV, TSV, or Excel file.`),this.format=format,this.name="UnreadableFormatError"}};function fileFormatOf(name){let m=/\.([a-z0-9]+)$/i.exec(name??"");return m?m[1].toLowerCase():""}async function readFile(input,options={}){let name=options.filename??(typeof Blob<"u"&&input instanceof Blob&&"name"in input?String(input.name):void 0),format=(options.format??fileFormatOf(name))||(typeof input=="string"?"csv":"");if(WORKBOOK_FORMATS.has(format)){let book=await readWorkbook(input,options.workbook);return{format:format==="xlsm"||format==="xlsb"?"xlsx":format,sheets:book.sheets.map(s=>({name:s.name,grid:s.grid(options.grid),merges:s.merges})),errors:[]}}if(!DELIMITED_FORMATS.has(format))throw new UnreadableFormatError(format||"unknown");let text=typeof input=="string"?input:new TextDecoder("utf-8").decode(await bytesOf(input)),parsed=(0,import_delimited2.parseDelimited)(text,{...options,header:!1,...options.delimiter?{}:format==="tsv"||format==="tab"?{delimiter:" "}:{}});return{format:format==="tab"?"tsv":format,sheets:[{name:name?.replace(/\.[^.]+$/,"")||"Sheet1",grid:parsed.data,merges:[]}],errors:parsed.errors,delimiter:parsed.meta.delimiter}}
|
|
6
2
|
//# sourceMappingURL=operate-read.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/operate-read.ts","../src/operate/read.ts"],"sourcesContent":["/** `@ai-matrx/alchemy/operate/read` — the one parse door (CSV/TSV parser; workbooks through the lazy SheetJS leaf). */\nexport {\n UnreadableFormatError,\n fileFormatOf,\n parseDelimited,\n readFile,\n readWorkbook,\n toDelimitedText,\n type DelimitedCell,\n type DelimitedMeta,\n type DelimitedParseError,\n type DelimitedParseOptions,\n type DelimitedResult,\n type ReadFileOptions,\n type ReadInput,\n type TabularFile,\n type TabularFormat,\n type TabularSheet,\n type Workbook,\n type WorkbookCell,\n type WorkbookCellAddress,\n type WorkbookCellJson,\n type WorkbookCellRange,\n type WorkbookGridOptions,\n type WorkbookJson,\n type WorkbookReadOptions,\n type WorkbookRecordOptions,\n type WorkbookSheet,\n} from \"./operate/read\";\n","/**\n * `@ai-matrx/alchemy/operate/read` — the one parse door (ALC-19 T4–T12). Write has `buildFile`; read has `readFile`.\n *\n * One engine per format across read and write:\n * - CSV / TSV / delimited text: THIS module's parser (no library), the reverse of the `csv`/`tsv`\n * serializers in `./core`. `parseDelimited` is Papa Parse–compatible in the options the platform\n * uses (auto delimiter + line ending, quotes, `skipEmptyLines` true | \"greedy\", `header`,\n * `transformHeader`, `dynamicTyping`) and answers the same `{ data, errors, meta }` shape and\n * error codes, so a repointed importer keeps its behaviour.\n * - XLSX / XLS / ODS: SheetJS, the library the `xlsx` writer uses, loaded on first use from the\n * `/operate/formats/xlsx` leaf (`readWorkbook`). A caller that needs spreadsheet typing of a CSV\n * (dates, numbers, formulas as SheetJS reads them) reads it through `readWorkbook` too.\n */\n\n// Through the PUBLIC `/operate` subpath, so this entry shares the transfer core at runtime instead of bundling a copy.\nimport { createDraft, directSource, projectSnapshot, serialize, type Column, type Json } from \"@ai-matrx/alchemy/operate\";\n\n// ─── Delimited text ───────────────────────────────────────────────────────────────────────────────\n\nexport type DelimitedParseError = {\n type: \"Quotes\" | \"Delimiter\" | \"FieldMismatch\";\n code: \"MissingQuotes\" | \"InvalidQuotes\" | \"UndetectableDelimiter\" | \"TooFewFields\" | \"TooManyFields\";\n message: string;\n /** 0-based index of the data row the error belongs to (absent for whole-file errors). */\n row?: number;\n};\n\nexport type DelimitedParseOptions = {\n /** Field delimiter. Absent → guessed from `,` `\\t` `|` `;` and the ASCII record/unit separators; `,` when none fits. */\n delimiter?: string;\n /** true: an exactly empty line is dropped; \"greedy\": a line of only whitespace is dropped too. */\n skipEmptyLines?: boolean | \"greedy\";\n /** Numbers, `true`/`false` and ISO date-times become typed values; an empty field becomes null. */\n dynamicTyping?: boolean;\n /** Header mode only: rename a header (e.g. trim + lowercase). */\n transformHeader?(header: string, index: number): string;\n};\n\nexport type DelimitedCell = string | number | boolean | Date | null;\nexport type DelimitedMeta = { delimiter: string; linebreak: string; fields?: string[] };\nexport type DelimitedResult<T> = { data: T[]; errors: DelimitedParseError[]; meta: DelimitedMeta };\n\nconst RECORD_SEP = String.fromCharCode(30);\nconst UNIT_SEP = String.fromCharCode(31);\nconst GUESS_DELIMITERS = [\",\", \"\\t\", \"|\", \";\", RECORD_SEP, UNIT_SEP];\nconst FLOAT = /^\\s*-?(\\d+\\.?|\\.\\d+|\\d+\\.\\d+)([eE][-+]?\\d+)?\\s*$/;\nconst ISO_DATE =\n /^((\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)))$/;\n\nfunction guessLinebreak(rawText: string): string {\n // Quoted cells may hold any line break; only the ones between records say what the file uses.\n const text = rawText.includes('\"') ? rawText.replace(/\"[^\"]*\"/g, '\"\"') : rawText;\n const crlf = text.indexOf(\"\\r\\n\");\n const lf = text.indexOf(\"\\n\");\n const cr = text.indexOf(\"\\r\");\n if (crlf !== -1 && crlf === cr) return \"\\r\\n\";\n if (cr !== -1 && (lf === -1 || cr < lf)) return \"\\r\";\n return \"\\n\";\n}\n\n/** Tokenize into rows of raw string fields; quote errors are reported against their data row. */\nfunction tokenize(text: string, delimiter: string, linebreak: string, preview = Infinity): { rows: string[][]; errors: DelimitedParseError[] } {\n const rows: string[][] = [];\n const errors: DelimitedParseError[] = [];\n if (text === \"\") return { rows, errors };\n let row: string[] = [];\n let i = 0;\n const n = text.length;\n const dl = delimiter.length;\n const ll = linebreak.length;\n while (i <= n && rows.length < preview) {\n if (text.startsWith('\"', i)) {\n // Quoted field: \"\" is a literal quote; the closing quote must precede a delimiter, a line break or the end.\n let value = \"\";\n let j = i + 1;\n let closed = false;\n while (j < n) {\n const q = text.indexOf('\"', j);\n if (q === -1) break;\n value += text.slice(j, q);\n if (text[q + 1] === '\"') {\n value += '\"';\n j = q + 2;\n continue;\n }\n const after = q + 1;\n if (after >= n || text.startsWith(delimiter, after) || text.startsWith(linebreak, after)) {\n closed = true;\n j = after;\n break;\n }\n errors.push({ type: \"Quotes\", code: \"InvalidQuotes\", message: \"Trailing quote on quoted field is malformed\", row: rows.length });\n value += '\"';\n j = after;\n }\n if (!closed) {\n errors.push({ type: \"Quotes\", code: \"MissingQuotes\", message: \"Quoted field unterminated\", row: rows.length });\n value += text.slice(j);\n row.push(value);\n rows.push(row);\n return { rows, errors };\n }\n row.push(value);\n i = j;\n if (i >= n) {\n rows.push(row);\n break;\n }\n if (text.startsWith(delimiter, i)) {\n i += dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n rows.push(row);\n row = [];\n i += ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n const nextDelim = text.indexOf(delimiter, i);\n const nextBreak = text.indexOf(linebreak, i);\n if (nextDelim !== -1 && (nextBreak === -1 || nextDelim < nextBreak)) {\n row.push(text.slice(i, nextDelim));\n i = nextDelim + dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n if (nextBreak !== -1) {\n row.push(text.slice(i, nextBreak));\n rows.push(row);\n row = [];\n i = nextBreak + ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n row.push(text.slice(i));\n rows.push(row);\n break;\n }\n return { rows, errors };\n}\n\nconst isEmptyRow = (row: readonly string[], mode: boolean | \"greedy\") =>\n mode === \"greedy\" ? row.join(\"\").trim() === \"\" : row.length === 1 && row[0]!.length === 0;\n\nfunction guessDelimiter(text: string, linebreak: string, skip: boolean | \"greedy\" | undefined): { delimiter: string; successful: boolean } {\n let best: string | undefined;\n let bestDelta: number | undefined;\n let bestAvg: number | undefined;\n for (const delim of GUESS_DELIMITERS) {\n const { rows } = tokenize(text, delim, linebreak, 10);\n let delta = 0;\n let avg = 0;\n let empty = 0;\n let prev: number | undefined;\n for (const row of rows) {\n if (skip && isEmptyRow(row, skip)) {\n empty++;\n continue;\n }\n const count = row.length;\n avg += count;\n if (prev === undefined) {\n prev = count;\n continue;\n }\n if (count > 0) {\n delta += Math.abs(count - prev);\n prev = count;\n }\n }\n if (rows.length > 0) avg /= rows.length - empty;\n if ((bestDelta === undefined || delta <= bestDelta) && (bestAvg === undefined || avg > bestAvg) && avg > 1.99) {\n bestDelta = delta;\n bestAvg = avg;\n best = delim;\n }\n }\n return { delimiter: best ?? \",\", successful: best !== undefined };\n}\n\nfunction typed(value: string): DelimitedCell {\n if (value === \"true\" || value === \"TRUE\") return true;\n if (value === \"false\" || value === \"FALSE\") return false;\n if (FLOAT.test(value)) {\n const num = parseFloat(value);\n if (num > -(2 ** 53) && num < 2 ** 53) return num;\n return value;\n }\n if (ISO_DATE.test(value)) return new Date(value);\n return value === \"\" ? null : value;\n}\n\n/** Parse delimited text into rows of fields (header row first). */\nexport function parseDelimited(text: string, options?: DelimitedParseOptions & { header?: false; dynamicTyping?: false }): DelimitedResult<string[]>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: false; dynamicTyping: true }): DelimitedResult<DelimitedCell[]>;\n/** Parse delimited text into records keyed by the (transformed) header row. Extra fields land in `__parsed_extra`. */\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping?: false }): DelimitedResult<Record<string, string>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping: true }): DelimitedResult<Record<string, DelimitedCell>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: boolean } = {}): DelimitedResult<unknown> {\n const input = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n const linebreak = guessLinebreak(input);\n const errors: DelimitedParseError[] = [];\n let delimiter = options.delimiter;\n if (!delimiter) {\n const guess = guessDelimiter(input, linebreak, options.skipEmptyLines);\n delimiter = guess.delimiter;\n if (!guess.successful)\n errors.push({ type: \"Delimiter\", code: \"UndetectableDelimiter\", message: \"Unable to auto-detect delimiting character; defaulted to ','\" });\n }\n const tokens = tokenize(input, delimiter, linebreak);\n errors.unshift(...tokens.errors);\n let rows = tokens.rows;\n if (options.skipEmptyLines) rows = rows.filter((row) => !isEmptyRow(row, options.skipEmptyLines!));\n const meta: DelimitedMeta = { delimiter, linebreak };\n if (!options.header) {\n return { data: options.dynamicTyping ? rows.map((row) => row.map(typed)) : rows, errors, meta };\n }\n const [head = [], ...body] = rows;\n const seen = new Map<string, number>();\n const fields = head.map((raw, index) => {\n const name = options.transformHeader ? options.transformHeader(raw, index) : raw;\n const count = seen.get(name) ?? 0;\n seen.set(name, count + 1);\n return count === 0 ? name : `${name}_${count}`;\n });\n meta.fields = fields;\n const data = body.map((row, index) => {\n const record: Record<string, unknown> = {};\n row.forEach((value, j) => {\n const cell = options.dynamicTyping ? typed(value) : value;\n if (j >= fields.length) {\n ((record.__parsed_extra as unknown[] | undefined) ??= []).push(cell);\n } else record[fields[j]!] = cell;\n });\n if (row.length > fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooManyFields\", message: `Too many fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n else if (row.length < fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooFewFields\", message: `Too few fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n return record;\n });\n return { data, errors, meta };\n}\n\n/**\n * Delimited text from a header row + body rows, through the SAME `csv`/`tsv` serializer the\n * Alchemy menu's copy and download use (quoting, nested values as compact JSON, `null` empty).\n * `spreadsheetSafe` defaults ON (kit `spreadsheetSafeByDefault`: csv/tsv are for spreadsheets) and\n * prefixes formula-looking text with `'`; pass `false` only for a lossless round-trip or AI-read text.\n */\nexport function toDelimitedText(\n header: readonly string[],\n rows: readonly (readonly (Json | undefined)[] | Readonly<Record<string, Json | undefined>>)[],\n options: { format?: \"csv\" | \"tsv\"; spreadsheetSafe?: boolean } = {},\n): string {\n const columns: Column[] = header.map((label, i) => ({ id: `c${i}`, label, path: `/c${i}`, visible: true }));\n const record = (row: (typeof rows)[number]): Record<string, Json> => {\n const out: Record<string, Json> = {};\n header.forEach((label, i) => {\n const v = Array.isArray(row) ? (row as readonly (Json | undefined)[])[i] : (row as Readonly<Record<string, Json | undefined>>)[label];\n if (v !== undefined) out[`c${i}`] = v;\n });\n return out;\n };\n const captured = directSource({ kind: \"rows\", columns, rows: rows.map(record) });\n const draft = createDraft(projectSnapshot(captured.snapshot, captured.projection));\n return serialize(draft, options.format ?? \"csv\", options.spreadsheetSafe === undefined ? {} : { spreadsheetSafe: options.spreadsheetSafe }).plainText;\n}\n\n// ─── Workbooks (the SheetJS leaf, loaded on first use) ───────────────────────────────────────────\n\nexport type WorkbookCellAddress = { r: number; c: number };\nexport type WorkbookCellRange = { s: WorkbookCellAddress; e: WorkbookCellAddress };\n/** A cell as the workbook engine reads it: type, raw value, formatted text, formula source. */\nexport type WorkbookCell = {\n t: \"s\" | \"n\" | \"b\" | \"d\" | \"e\" | \"z\";\n v?: string | number | boolean | Date;\n w?: string;\n f?: string;\n styled: boolean;\n};\n/** Grid shape options (header row included): `raw: false` → formatted text; `defval` fills gaps. */\nexport type WorkbookGridOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport type WorkbookRecordOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport interface WorkbookSheet {\n name: string;\n /** The used range, null for a sheet without one. */\n range: WorkbookCellRange | null;\n merges: WorkbookCellRange[];\n cell(r: number, c: number): WorkbookCell | null;\n cells(): Iterable<readonly [WorkbookCellAddress, WorkbookCell]>;\n grid(options?: WorkbookGridOptions): unknown[][];\n records(options?: WorkbookRecordOptions): Record<string, unknown>[];\n}\nexport interface Workbook {\n sheetNames: string[];\n sheets: WorkbookSheet[];\n sheet(name: string): WorkbookSheet | undefined;\n}\n/** `dates`: date cells as Date (`t: \"d\"`); `formulas`: keep formula source; `styles`: read cell styles. */\nexport type WorkbookReadOptions = { dates?: boolean; formulas?: boolean; styles?: boolean };\n\n/** A typed cell for the `xlsx-workbook` format; a bare string/number/boolean is a value cell. */\nexport type WorkbookCellJson = { t?: \"s\" | \"n\" | \"b\"; v?: string | number | boolean | null; f?: string };\n/** The `xlsx-workbook` payload value (`{ kind: \"registered\", format: \"workbook\", value }`). */\nexport type WorkbookJson = {\n sheets: { name: string; rows: (WorkbookCellJson | string | number | boolean | null)[][]; merges?: WorkbookCellRange[] }[];\n};\n\nexport type ReadInput = Blob | ArrayBuffer | Uint8Array | string;\n\nasync function bytesOf(input: ReadInput): Promise<Uint8Array> {\n if (typeof input === \"string\") return new TextEncoder().encode(input);\n if (input instanceof Uint8Array) return input;\n if (input instanceof ArrayBuffer) return new Uint8Array(input);\n return new Uint8Array(await input.arrayBuffer());\n}\n\n/** Read a spreadsheet (XLSX, XLS, ODS; CSV/TSV with spreadsheet typing) through the one workbook engine. */\nexport async function readWorkbook(input: ReadInput, options: WorkbookReadOptions = {}): Promise<Workbook> {\n const bytes = await bytesOf(input);\n const { readWorkbookBytes } = await import(\"@ai-matrx/alchemy/operate/formats/xlsx\");\n return readWorkbookBytes(bytes, options);\n}\n\n// ─── readFile: the door ──────────────────────────────────────────────────────────────────────────\n\nexport type TabularFormat = \"csv\" | \"tsv\" | \"txt\" | \"xlsx\" | \"xls\" | \"ods\";\nconst WORKBOOK_FORMATS: ReadonlySet<string> = new Set([\"xlsx\", \"xls\", \"ods\", \"xlsm\", \"xlsb\"]);\nconst DELIMITED_FORMATS: ReadonlySet<string> = new Set([\"csv\", \"tsv\", \"txt\", \"tab\"]);\n\nexport type ReadFileOptions = DelimitedParseOptions & {\n /** The file's format; absent → from `filename` (or a File's name) extension. */\n format?: TabularFormat | (string & {});\n filename?: string;\n /** Workbooks: the grid shape (`sheet_to_json` header-row semantics). */\n grid?: WorkbookGridOptions;\n /** Workbooks: read options. */\n workbook?: WorkbookReadOptions;\n};\nexport type TabularSheet = { name: string; grid: unknown[][]; merges: WorkbookCellRange[] };\nexport type TabularFile = { format: TabularFormat; sheets: TabularSheet[]; errors: DelimitedParseError[]; delimiter?: string };\n\nexport class UnreadableFormatError extends Error {\n readonly format: string;\n constructor(format: string) {\n super(`A \"${format}\" file is not a table we can read. Choose a CSV, TSV, or Excel file.`);\n this.format = format;\n this.name = \"UnreadableFormatError\";\n }\n}\n\n/** The format a file name declares (lower-case extension), or \"\" when it has none. */\nexport function fileFormatOf(name: string | undefined): string {\n const m = /\\.([a-z0-9]+)$/i.exec(name ?? \"\");\n return m ? m[1]!.toLowerCase() : \"\";\n}\n\n/**\n * Read a table file — CSV/TSV/plain text through the delimited parser, XLSX/XLS/ODS through the\n * workbook engine — into sheets of grids (header row first). Delimited text is one sheet.\n */\nexport async function readFile(input: ReadInput, options: ReadFileOptions = {}): Promise<TabularFile> {\n const name = options.filename ?? (typeof Blob !== \"undefined\" && input instanceof Blob && \"name\" in input ? String((input as File).name) : undefined);\n const format = (options.format ?? fileFormatOf(name)) || (typeof input === \"string\" ? \"csv\" : \"\");\n if (WORKBOOK_FORMATS.has(format)) {\n const book = await readWorkbook(input, options.workbook);\n return {\n format: (format === \"xlsm\" || format === \"xlsb\" ? \"xlsx\" : format) as TabularFormat,\n sheets: book.sheets.map((s) => ({ name: s.name, grid: s.grid(options.grid), merges: s.merges })),\n errors: [],\n };\n }\n if (!DELIMITED_FORMATS.has(format)) throw new UnreadableFormatError(format || \"unknown\");\n const text = typeof input === \"string\" ? input : new TextDecoder(\"utf-8\").decode(await bytesOf(input));\n const parsed = parseDelimited(text, {\n ...options,\n header: false,\n ...(options.delimiter ? {} : format === \"tsv\" || format === \"tab\" ? { delimiter: \"\\t\" } : {}),\n } as DelimitedParseOptions & { header: false; dynamicTyping?: false });\n return {\n format: (format === \"tab\" ? \"tsv\" : format) as TabularFormat,\n sheets: [{ name: name?.replace(/\\.[^.]+$/, \"\") || \"Sheet1\", grid: parsed.data, merges: [] }],\n errors: parsed.errors,\n delimiter: parsed.meta.delimiter,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/operate-read.ts","../src/operate/read.ts"],"sourcesContent":["/** `@ai-matrx/alchemy/operate/read` — the one parse door (CSV/TSV parser; workbooks through the lazy SheetJS leaf). */\nexport {\n UnreadableFormatError,\n fileFormatOf,\n parseDelimited,\n readFile,\n readWorkbook,\n toDelimitedText,\n type DelimitedCell,\n type DelimitedMeta,\n type DelimitedParseError,\n type DelimitedParseOptions,\n type DelimitedResult,\n type ReadFileOptions,\n type ReadInput,\n type TabularFile,\n type TabularFormat,\n type TabularSheet,\n type Workbook,\n type WorkbookCell,\n type WorkbookCellAddress,\n type WorkbookCellJson,\n type WorkbookCellRange,\n type WorkbookGridOptions,\n type WorkbookJson,\n type WorkbookReadOptions,\n type WorkbookRecordOptions,\n type WorkbookSheet,\n} from \"./operate/read\";\n","/**\n * `@ai-matrx/alchemy/operate/read` — the one parse door (ALC-19 T4–T12). Write has `buildFile`; read has `readFile`.\n *\n * One engine per format across read and write:\n * - CSV / TSV / delimited text: kit's `parseDelimited` (no library; it sits in `@ai-matrx/kit/delimited`\n * beside the one writer so packages below alchemy read through it too), re-exported here. `parseDelimited` is Papa Parse–compatible in the options the platform\n * uses (auto delimiter + line ending, quotes, `skipEmptyLines` true | \"greedy\", `header`,\n * `transformHeader`, `dynamicTyping`) and answers the same `{ data, errors, meta }` shape and\n * error codes, so a repointed importer keeps its behaviour.\n * - XLSX / XLS / ODS: SheetJS, the library the `xlsx` writer uses, loaded on first use from the\n * `/operate/formats/xlsx` leaf (`readWorkbook`). A caller that needs spreadsheet typing of a CSV\n * (dates, numbers, formulas as SheetJS reads them) reads it through `readWorkbook` too.\n */\n\n// Through the PUBLIC `/operate` subpath, so this entry shares the transfer core at runtime instead of bundling a copy.\nimport { createDraft, directSource, projectSnapshot, serialize, type Column, type Json } from \"@ai-matrx/alchemy/operate\";\n\n// ─── Delimited text ───────────────────────────────────────────────────────────────────────────────\n// THE one parser lives beside THE one writer in @ai-matrx/kit/delimited (AP-2: packages below\n// alchemy read CSV too); this door re-exports it unchanged, so `/operate/read` keeps its API.\nexport { parseDelimited } from \"@ai-matrx/kit/delimited\";\nexport type {\n DelimitedParseError,\n DelimitedParseOptions,\n ParsedDelimitedCell as DelimitedCell,\n DelimitedMeta,\n DelimitedResult,\n} from \"@ai-matrx/kit/delimited\";\nimport { parseDelimited, type DelimitedParseError, type DelimitedParseOptions } from \"@ai-matrx/kit/delimited\";\n\n/**\n * Delimited text from a header row + body rows, through the SAME `csv`/`tsv` serializer the\n * Alchemy menu's copy and download use (quoting, nested values as compact JSON, `null` empty).\n * `spreadsheetSafe` defaults ON (kit `spreadsheetSafeByDefault`: csv/tsv are for spreadsheets) and\n * prefixes formula-looking text with `'`; pass `false` only for a lossless round-trip or AI-read text.\n */\nexport function toDelimitedText(\n header: readonly string[],\n rows: readonly (readonly (Json | undefined)[] | Readonly<Record<string, Json | undefined>>)[],\n options: { format?: \"csv\" | \"tsv\"; spreadsheetSafe?: boolean } = {},\n): string {\n const columns: Column[] = header.map((label, i) => ({ id: `c${i}`, label, path: `/c${i}`, visible: true }));\n const record = (row: (typeof rows)[number]): Record<string, Json> => {\n const out: Record<string, Json> = {};\n header.forEach((label, i) => {\n const v = Array.isArray(row) ? (row as readonly (Json | undefined)[])[i] : (row as Readonly<Record<string, Json | undefined>>)[label];\n if (v !== undefined) out[`c${i}`] = v;\n });\n return out;\n };\n const captured = directSource({ kind: \"rows\", columns, rows: rows.map(record) });\n const draft = createDraft(projectSnapshot(captured.snapshot, captured.projection));\n return serialize(draft, options.format ?? \"csv\", options.spreadsheetSafe === undefined ? {} : { spreadsheetSafe: options.spreadsheetSafe }).plainText;\n}\n\n// ─── Workbooks (the SheetJS leaf, loaded on first use) ───────────────────────────────────────────\n\nexport type WorkbookCellAddress = { r: number; c: number };\nexport type WorkbookCellRange = { s: WorkbookCellAddress; e: WorkbookCellAddress };\n/** A cell as the workbook engine reads it: type, raw value, formatted text, formula source. */\nexport type WorkbookCell = {\n t: \"s\" | \"n\" | \"b\" | \"d\" | \"e\" | \"z\";\n v?: string | number | boolean | Date;\n w?: string;\n f?: string;\n styled: boolean;\n};\n/** Grid shape options (header row included): `raw: false` → formatted text; `defval` fills gaps. */\nexport type WorkbookGridOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport type WorkbookRecordOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport interface WorkbookSheet {\n name: string;\n /** The used range, null for a sheet without one. */\n range: WorkbookCellRange | null;\n merges: WorkbookCellRange[];\n cell(r: number, c: number): WorkbookCell | null;\n cells(): Iterable<readonly [WorkbookCellAddress, WorkbookCell]>;\n grid(options?: WorkbookGridOptions): unknown[][];\n records(options?: WorkbookRecordOptions): Record<string, unknown>[];\n}\nexport interface Workbook {\n sheetNames: string[];\n sheets: WorkbookSheet[];\n sheet(name: string): WorkbookSheet | undefined;\n}\n/** `dates`: date cells as Date (`t: \"d\"`); `formulas`: keep formula source; `styles`: read cell styles. */\nexport type WorkbookReadOptions = { dates?: boolean; formulas?: boolean; styles?: boolean };\n\n/** A typed cell for the `xlsx-workbook` format; a bare string/number/boolean is a value cell. */\nexport type WorkbookCellJson = { t?: \"s\" | \"n\" | \"b\"; v?: string | number | boolean | null; f?: string };\n/** The `xlsx-workbook` payload value (`{ kind: \"registered\", format: \"workbook\", value }`). */\nexport type WorkbookJson = {\n sheets: { name: string; rows: (WorkbookCellJson | string | number | boolean | null)[][]; merges?: WorkbookCellRange[] }[];\n};\n\nexport type ReadInput = Blob | ArrayBuffer | Uint8Array | string;\n\nasync function bytesOf(input: ReadInput): Promise<Uint8Array> {\n if (typeof input === \"string\") return new TextEncoder().encode(input);\n if (input instanceof Uint8Array) return input;\n if (input instanceof ArrayBuffer) return new Uint8Array(input);\n return new Uint8Array(await input.arrayBuffer());\n}\n\n/** Read a spreadsheet (XLSX, XLS, ODS; CSV/TSV with spreadsheet typing) through the one workbook engine. */\nexport async function readWorkbook(input: ReadInput, options: WorkbookReadOptions = {}): Promise<Workbook> {\n const bytes = await bytesOf(input);\n const { readWorkbookBytes } = await import(\"@ai-matrx/alchemy/operate/formats/xlsx\");\n return readWorkbookBytes(bytes, options);\n}\n\n// ─── readFile: the door ──────────────────────────────────────────────────────────────────────────\n\nexport type TabularFormat = \"csv\" | \"tsv\" | \"txt\" | \"xlsx\" | \"xls\" | \"ods\";\nconst WORKBOOK_FORMATS: ReadonlySet<string> = new Set([\"xlsx\", \"xls\", \"ods\", \"xlsm\", \"xlsb\"]);\nconst DELIMITED_FORMATS: ReadonlySet<string> = new Set([\"csv\", \"tsv\", \"txt\", \"tab\"]);\n\nexport type ReadFileOptions = DelimitedParseOptions & {\n /** The file's format; absent → from `filename` (or a File's name) extension. */\n format?: TabularFormat | (string & {});\n filename?: string;\n /** Workbooks: the grid shape (`sheet_to_json` header-row semantics). */\n grid?: WorkbookGridOptions;\n /** Workbooks: read options. */\n workbook?: WorkbookReadOptions;\n};\nexport type TabularSheet = { name: string; grid: unknown[][]; merges: WorkbookCellRange[] };\nexport type TabularFile = { format: TabularFormat; sheets: TabularSheet[]; errors: DelimitedParseError[]; delimiter?: string };\n\nexport class UnreadableFormatError extends Error {\n readonly format: string;\n constructor(format: string) {\n super(`A \"${format}\" file is not a table we can read. Choose a CSV, TSV, or Excel file.`);\n this.format = format;\n this.name = \"UnreadableFormatError\";\n }\n}\n\n/** The format a file name declares (lower-case extension), or \"\" when it has none. */\nexport function fileFormatOf(name: string | undefined): string {\n const m = /\\.([a-z0-9]+)$/i.exec(name ?? \"\");\n return m ? m[1]!.toLowerCase() : \"\";\n}\n\n/**\n * Read a table file — CSV/TSV/plain text through the delimited parser, XLSX/XLS/ODS through the\n * workbook engine — into sheets of grids (header row first). Delimited text is one sheet.\n */\nexport async function readFile(input: ReadInput, options: ReadFileOptions = {}): Promise<TabularFile> {\n const name = options.filename ?? (typeof Blob !== \"undefined\" && input instanceof Blob && \"name\" in input ? String((input as File).name) : undefined);\n const format = (options.format ?? fileFormatOf(name)) || (typeof input === \"string\" ? \"csv\" : \"\");\n if (WORKBOOK_FORMATS.has(format)) {\n const book = await readWorkbook(input, options.workbook);\n return {\n format: (format === \"xlsm\" || format === \"xlsb\" ? \"xlsx\" : format) as TabularFormat,\n sheets: book.sheets.map((s) => ({ name: s.name, grid: s.grid(options.grid), merges: s.merges })),\n errors: [],\n };\n }\n if (!DELIMITED_FORMATS.has(format)) throw new UnreadableFormatError(format || \"unknown\");\n const text = typeof input === \"string\" ? input : new TextDecoder(\"utf-8\").decode(await bytesOf(input));\n const parsed = parseDelimited(text, {\n ...options,\n header: false,\n ...(options.delimiter ? {} : format === \"tsv\" || format === \"tab\" ? { delimiter: \"\\t\" } : {}),\n } as DelimitedParseOptions & { header: false; dynamicTyping?: false });\n return {\n format: (format === \"tab\" ? \"tsv\" : format) as TabularFormat,\n sheets: [{ name: name?.replace(/\\.[^.]+$/, \"\") || \"Sheet1\", grid: parsed.data, merges: [] }],\n errors: parsed.errors,\n delimiter: parsed.meta.delimiter,\n };\n}\n"],"mappings":"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","names":["import_delimited"]}
|