@graphein/node 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -122,6 +122,6 @@ declare function renderChart(spec: AnySpec, options?: RenderImageOptions): NodeR
|
|
|
122
122
|
*/
|
|
123
123
|
declare function renderToPNG(spec: AnySpec, options?: RenderImageOptions): Buffer;
|
|
124
124
|
|
|
125
|
-
declare const VERSION = "0.
|
|
125
|
+
declare const VERSION = "0.21.0";
|
|
126
126
|
|
|
127
127
|
export { type FontFace, type FontRegistration, NodeRenderError, type NodeRenderResult, RENDER_LIMITS, type RenderImageOptions, type ResolvedRenderImageOptions, VERSION, renderChart, renderToPNG, validateRenderImageOptions };
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@graphein/node` — headless Graphein rendering for Node.\n *\n * Render any canvas-backed {@link ChartSpec} to a PNG buffer **and** its\n * {@link RenderReport} with no browser and no JSDOM, so the whole\n * generate → validate → render → critique loop can run server-side (CI, agents,\n * email/PDF report assets). It wires `graphein`'s dependency-free\n * `renderToContext` to `@napi-rs/canvas`; the core engine stays zero-dependency.\n *\n * ```ts\n * import { renderChart } from '@graphein/node';\n * const { png, report } = renderChart(spec, { width: 900, height: 480, dpr: 2 });\n * if (!report.ok) console.warn(report.diagnostics);\n * await fs.writeFile('chart.png', png);\n * ```\n *\n * Every chart type rasterizes — kpi/table/matrix/slicers/dashboard render a\n * static canvas snapshot, so the whole catalog can be validated server-side.\n */\n\nimport { createCanvas, GlobalFonts } from '@napi-rs/canvas';\nimport {\n draft,\n type AnySpec, type ChartSpec, type DashboardSpec, type DraftResult,\n type AnyRenderReport, type DashboardRenderReport, type QualityOptions, type QualityResult, type RenderReport,\n type ValidationError,\n} from 'graphein';\nimport {\n computeBackingSize,\n renderDashboardToContext,\n renderToContext,\n setMeasureContext,\n} from 'graphein/internal';\n\n/** A font file to register before rendering, improving text fidelity. */\nexport interface FontFace {\n /** Absolute path to a `.ttf` / `.otf` / `.woff` file. */\n path: string;\n /**\n * The family name to register the font under. Match this to the family your\n * theme uses (Graphein's default is `Inter`) so text renders in that face.\n */\n family: string;\n}\n\n/** Options for {@link renderChart} / {@link renderToPNG}. */\nexport interface RenderImageOptions {\n /** Logical width in CSS pixels. Default `800`. */\n width?: number;\n /** Logical height in CSS pixels. Default `500`. */\n height?: number;\n /**\n * Device pixel ratio. The PNG is rasterized at `width * dpr × height * dpr`\n * for crisp output. Default `2`.\n */\n dpr?: number;\n /** Extra fonts to register before rendering. */\n fonts?: FontFace[];\n /** Opt in to core's presentation-default loop (at most three whole-visual render attempts). */\n quality?: boolean | QualityOptions;\n}\n\n/** Registration evidence for a caller-supplied font; failure is non-fatal. */\nexport interface FontRegistration {\n path: string;\n family: string;\n registered: boolean;\n message?: string;\n}\n\n/** The result of a headless render: the PNG bytes plus the render report. */\nexport interface NodeRenderResult<R extends AnyRenderReport = RenderReport> {\n /** PNG-encoded image bytes. */\n png: Buffer;\n /**\n * The same machine-readable diagnostics as `instance.report()` in the\n * browser — `ok`, mark/series/color counts, and any clipping / overlap /\n * contrast warnings. Lets an agent critique the chart with no vision model.\n */\n report: R;\n /** Pixel width of the PNG (`width * dpr`, rounded). */\n width: number;\n /** Pixel height of the PNG (`height * dpr`, rounded). */\n height: number;\n /** Effective spec corresponding to the final PNG and report. Input data is never transformed by quality mode. */\n spec: AnySpec;\n /** Core quality-loop evidence, present only when opted in. */\n quality?: QualityResult;\n /** Validation advisories retained when quality mode validates the spec; absent on the direct default path. */\n warnings?: ValidationError[];\n /** Outcome of each explicitly requested font registration. */\n fontRegistrations: FontRegistration[];\n}\n\n/** Opt-in validation/rendering failed, so no potentially mismatched PNG was encoded. */\nexport class NodeRenderError extends Error {\n constructor(readonly result: DraftResult) {\n super((result.renderError ?? result.errors.map((e) => `${e.path}: ${e.message}`).join('; ')) ||\n 'Graphein: no final render report is available.');\n this.name = 'NodeRenderError';\n }\n}\n\nconst DEFAULT_WIDTH = 800;\nconst DEFAULT_HEIGHT = 500;\nconst DEFAULT_DPR = 2;\n\n/** Hard bounds that keep headless renders from creating invalid or enormous canvases. */\nexport const RENDER_LIMITS = Object.freeze({\n minWidth: 1,\n maxWidth: 8192,\n minHeight: 1,\n maxHeight: 8192,\n minDpr: 0.25,\n maxDpr: 4,\n maxPixels: 67_108_864,\n});\n\nexport interface ResolvedRenderImageOptions {\n /** Logical width in CSS pixels. */\n width: number;\n /** Logical height in CSS pixels. */\n height: number;\n /** Device pixel ratio. */\n dpr: number;\n /** Rounded raster width in physical pixels. */\n pixelWidth: number;\n /** Rounded raster height in physical pixels. */\n pixelHeight: number;\n}\n\nfunction assertFiniteInRange(name: string, value: number, min: number, max: number, unit: string): void {\n if (!Number.isFinite(value) || value < min || value > max) {\n throw new RangeError(`Graphein render option \"${name}\" must be a finite number between ${min} and ${max}${unit ? ` ${unit}` : ''}.`);\n }\n}\n\n/** Validate and resolve render sizing options before allocating a canvas. */\nexport function validateRenderImageOptions(options: RenderImageOptions = {}): ResolvedRenderImageOptions {\n const width = options.width ?? DEFAULT_WIDTH;\n const height = options.height ?? DEFAULT_HEIGHT;\n const dpr = options.dpr ?? DEFAULT_DPR;\n assertFiniteInRange('width', width, RENDER_LIMITS.minWidth, RENDER_LIMITS.maxWidth, 'CSS pixels');\n assertFiniteInRange('height', height, RENDER_LIMITS.minHeight, RENDER_LIMITS.maxHeight, 'CSS pixels');\n assertFiniteInRange('dpr', dpr, RENDER_LIMITS.minDpr, RENDER_LIMITS.maxDpr, '');\n const pixelWidth = Math.round(width * dpr);\n const pixelHeight = Math.round(height * dpr);\n const pixels = pixelWidth * pixelHeight;\n if (!Number.isSafeInteger(pixels) || pixels > RENDER_LIMITS.maxPixels) {\n throw new RangeError(\n `Graphein render size ${pixelWidth}×${pixelHeight} (${pixels.toLocaleString('en-US')} pixels) exceeds the ${RENDER_LIMITS.maxPixels.toLocaleString('en-US')}-pixel cap. Reduce width, height, or dpr.`,\n );\n }\n return { width, height, dpr, pixelWidth, pixelHeight };\n}\n\nfunction registerFonts(fonts: FontFace[] | undefined): FontRegistration[] {\n return (fonts ?? []).map((f) => {\n try {\n const registered = !!GlobalFonts.registerFromPath(f.path, f.family);\n return { ...f, registered, ...(registered ? {} : { message: 'Font registration returned no font.' }) };\n } catch (error) {\n return { ...f, registered: false, message: error instanceof Error ? error.message : String(error) };\n }\n });\n}\n\nfunction withFontFindings<R extends AnyRenderReport>(report: R, fonts: FontRegistration[]): R {\n const failed = fonts.filter((f) => !f.registered);\n if (!failed.length) return report;\n return {\n ...report,\n ok: false,\n diagnostics: [\n ...report.diagnostics.filter((d) => d.severity === 'error'),\n ...failed.map((f) => ({\n code: 'font-fallback', severity: 'warning' as const,\n message: `Could not register \"${f.family}\"; text may use an available fallback font.`,\n details: { family: f.family, path: f.path, reason: f.message },\n hint: 'Supply a readable font file and register it under the family used by the theme.',\n })),\n ...report.diagnostics.filter((d) => d.severity !== 'error'),\n ],\n };\n}\n\n/**\n * Render `spec` to a PNG buffer and its {@link RenderReport}, entirely in Node.\n *\n * Supports the entire catalog: line, area, bar, scatter, box, pie, heatmap,\n * sankey, choropleth, combo, histogram, funnel, and the formerly DOM-only kpi,\n * table, matrix, slicers and dashboard (painted as a static canvas snapshot).\n */\nexport function renderChart(spec: DashboardSpec, options?: RenderImageOptions): NodeRenderResult<DashboardRenderReport>;\nexport function renderChart(spec: ChartSpec, options?: RenderImageOptions): NodeRenderResult;\nexport function renderChart(spec: AnySpec, options?: RenderImageOptions): NodeRenderResult<AnyRenderReport>;\nexport function renderChart(spec: AnySpec, options: RenderImageOptions = {}): NodeRenderResult<AnyRenderReport> {\n const { width, height, dpr } = validateRenderImageOptions(options);\n\n const fontRegistrations = registerFonts(options.fonts);\n\n const backing = computeBackingSize(width, height, dpr);\n const pxW = backing.pixelWidth;\n const pxH = backing.pixelHeight;\n\n const canvas = createCanvas(pxW, pxH);\n const ctx = canvas.getContext('2d');\n ctx.scale(backing.dpr, backing.dpr);\n\n // A dedicated 1×1 context for text measurement so layout uses real font\n // metrics (not the SSR heuristic) without mutating the drawing context.\n const measureCtx = createCanvas(1, 1).getContext('2d');\n\n // The interaction layer is unused on the static path; give it its own canvas\n // so a stray hover-layer paint can never land on the marks canvas.\n const interactionCtx = createCanvas(pxW, pxH).getContext('2d');\n interactionCtx.scale(backing.dpr, backing.dpr);\n\n const restoreMeasureContext = setMeasureContext(measureCtx as unknown as CanvasRenderingContext2D);\n let report: AnyRenderReport;\n let effectiveSpec = spec;\n let quality: QualityResult | undefined;\n let warnings: ValidationError[] | undefined;\n try {\n const target = {\n marks: ctx as unknown as CanvasRenderingContext2D,\n interaction: interactionCtx as unknown as CanvasRenderingContext2D,\n width,\n height,\n backingStore: {\n requestedDpr: backing.requestedDpr,\n effectiveDpr: backing.dpr,\n pixelWidth: backing.pixelWidth,\n pixelHeight: backing.pixelHeight,\n clamped: backing.clamped,\n },\n };\n if (options.quality) {\n const selected = draft(spec, { target, repair: false, quality: options.quality });\n if (!selected.report) throw new NodeRenderError(selected);\n report = selected.report;\n effectiveSpec = selected.spec as AnySpec;\n quality = selected.quality;\n warnings = selected.warnings;\n } else {\n report = spec.type === 'dashboard'\n ? renderDashboardToContext(target, spec)\n : renderToContext(target, spec);\n }\n } finally {\n restoreMeasureContext();\n }\n\n if (quality) {\n for (const iteration of quality.iterations) {\n if (iteration.report) iteration.report = withFontFindings(iteration.report, fontRegistrations);\n }\n report = quality.iterations.find((i) => i.pass === quality.selectedPass)?.report ?? report;\n if (!report.ok && quality.stopReason === 'satisfied') quality.stopReason = 'no-actions';\n } else {\n report = withFontFindings(report, fontRegistrations);\n }\n return {\n png: canvas.toBuffer('image/png'), report, spec: effectiveSpec, quality,\n ...(warnings ? { warnings } : {}),\n fontRegistrations, width: pxW, height: pxH,\n };\n}\n\n/**\n * Convenience wrapper around {@link renderChart} that returns only the PNG bytes.\n */\nexport function renderToPNG(spec: AnySpec, options?: RenderImageOptions): Buffer {\n return renderChart(spec, options).png;\n}\n\n/** Re-exported so callers can register fonts globally without a second dep. */\nexport { GlobalFonts };\nexport type {\n AnySpec, ChartSpec, DashboardSpec, QualityOptions, QualityResult,\n AnyRenderReport, DashboardRenderReport, RenderReport,\n ValidationError,\n} from 'graphein';\n\nexport const VERSION = '0.20.0';\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@graphein/node` — headless Graphein rendering for Node.\n *\n * Render any canvas-backed {@link ChartSpec} to a PNG buffer **and** its\n * {@link RenderReport} with no browser and no JSDOM, so the whole\n * generate → validate → render → critique loop can run server-side (CI, agents,\n * email/PDF report assets). It wires `graphein`'s dependency-free\n * `renderToContext` to `@napi-rs/canvas`; the core engine stays zero-dependency.\n *\n * ```ts\n * import { renderChart } from '@graphein/node';\n * const { png, report } = renderChart(spec, { width: 900, height: 480, dpr: 2 });\n * if (!report.ok) console.warn(report.diagnostics);\n * await fs.writeFile('chart.png', png);\n * ```\n *\n * Every chart type rasterizes — kpi/table/matrix/slicers/dashboard render a\n * static canvas snapshot, so the whole catalog can be validated server-side.\n */\n\nimport { createCanvas, GlobalFonts } from '@napi-rs/canvas';\nimport {\n draft,\n type AnySpec, type ChartSpec, type DashboardSpec, type DraftResult,\n type AnyRenderReport, type DashboardRenderReport, type QualityOptions, type QualityResult, type RenderReport,\n type ValidationError,\n} from 'graphein';\nimport {\n computeBackingSize,\n renderDashboardToContext,\n renderToContext,\n setMeasureContext,\n} from 'graphein/internal';\n\n/** A font file to register before rendering, improving text fidelity. */\nexport interface FontFace {\n /** Absolute path to a `.ttf` / `.otf` / `.woff` file. */\n path: string;\n /**\n * The family name to register the font under. Match this to the family your\n * theme uses (Graphein's default is `Inter`) so text renders in that face.\n */\n family: string;\n}\n\n/** Options for {@link renderChart} / {@link renderToPNG}. */\nexport interface RenderImageOptions {\n /** Logical width in CSS pixels. Default `800`. */\n width?: number;\n /** Logical height in CSS pixels. Default `500`. */\n height?: number;\n /**\n * Device pixel ratio. The PNG is rasterized at `width * dpr × height * dpr`\n * for crisp output. Default `2`.\n */\n dpr?: number;\n /** Extra fonts to register before rendering. */\n fonts?: FontFace[];\n /** Opt in to core's presentation-default loop (at most three whole-visual render attempts). */\n quality?: boolean | QualityOptions;\n}\n\n/** Registration evidence for a caller-supplied font; failure is non-fatal. */\nexport interface FontRegistration {\n path: string;\n family: string;\n registered: boolean;\n message?: string;\n}\n\n/** The result of a headless render: the PNG bytes plus the render report. */\nexport interface NodeRenderResult<R extends AnyRenderReport = RenderReport> {\n /** PNG-encoded image bytes. */\n png: Buffer;\n /**\n * The same machine-readable diagnostics as `instance.report()` in the\n * browser — `ok`, mark/series/color counts, and any clipping / overlap /\n * contrast warnings. Lets an agent critique the chart with no vision model.\n */\n report: R;\n /** Pixel width of the PNG (`width * dpr`, rounded). */\n width: number;\n /** Pixel height of the PNG (`height * dpr`, rounded). */\n height: number;\n /** Effective spec corresponding to the final PNG and report. Input data is never transformed by quality mode. */\n spec: AnySpec;\n /** Core quality-loop evidence, present only when opted in. */\n quality?: QualityResult;\n /** Validation advisories retained when quality mode validates the spec; absent on the direct default path. */\n warnings?: ValidationError[];\n /** Outcome of each explicitly requested font registration. */\n fontRegistrations: FontRegistration[];\n}\n\n/** Opt-in validation/rendering failed, so no potentially mismatched PNG was encoded. */\nexport class NodeRenderError extends Error {\n constructor(readonly result: DraftResult) {\n super((result.renderError ?? result.errors.map((e) => `${e.path}: ${e.message}`).join('; ')) ||\n 'Graphein: no final render report is available.');\n this.name = 'NodeRenderError';\n }\n}\n\nconst DEFAULT_WIDTH = 800;\nconst DEFAULT_HEIGHT = 500;\nconst DEFAULT_DPR = 2;\n\n/** Hard bounds that keep headless renders from creating invalid or enormous canvases. */\nexport const RENDER_LIMITS = Object.freeze({\n minWidth: 1,\n maxWidth: 8192,\n minHeight: 1,\n maxHeight: 8192,\n minDpr: 0.25,\n maxDpr: 4,\n maxPixels: 67_108_864,\n});\n\nexport interface ResolvedRenderImageOptions {\n /** Logical width in CSS pixels. */\n width: number;\n /** Logical height in CSS pixels. */\n height: number;\n /** Device pixel ratio. */\n dpr: number;\n /** Rounded raster width in physical pixels. */\n pixelWidth: number;\n /** Rounded raster height in physical pixels. */\n pixelHeight: number;\n}\n\nfunction assertFiniteInRange(name: string, value: number, min: number, max: number, unit: string): void {\n if (!Number.isFinite(value) || value < min || value > max) {\n throw new RangeError(`Graphein render option \"${name}\" must be a finite number between ${min} and ${max}${unit ? ` ${unit}` : ''}.`);\n }\n}\n\n/** Validate and resolve render sizing options before allocating a canvas. */\nexport function validateRenderImageOptions(options: RenderImageOptions = {}): ResolvedRenderImageOptions {\n const width = options.width ?? DEFAULT_WIDTH;\n const height = options.height ?? DEFAULT_HEIGHT;\n const dpr = options.dpr ?? DEFAULT_DPR;\n assertFiniteInRange('width', width, RENDER_LIMITS.minWidth, RENDER_LIMITS.maxWidth, 'CSS pixels');\n assertFiniteInRange('height', height, RENDER_LIMITS.minHeight, RENDER_LIMITS.maxHeight, 'CSS pixels');\n assertFiniteInRange('dpr', dpr, RENDER_LIMITS.minDpr, RENDER_LIMITS.maxDpr, '');\n const pixelWidth = Math.round(width * dpr);\n const pixelHeight = Math.round(height * dpr);\n const pixels = pixelWidth * pixelHeight;\n if (!Number.isSafeInteger(pixels) || pixels > RENDER_LIMITS.maxPixels) {\n throw new RangeError(\n `Graphein render size ${pixelWidth}×${pixelHeight} (${pixels.toLocaleString('en-US')} pixels) exceeds the ${RENDER_LIMITS.maxPixels.toLocaleString('en-US')}-pixel cap. Reduce width, height, or dpr.`,\n );\n }\n return { width, height, dpr, pixelWidth, pixelHeight };\n}\n\nfunction registerFonts(fonts: FontFace[] | undefined): FontRegistration[] {\n return (fonts ?? []).map((f) => {\n try {\n const registered = !!GlobalFonts.registerFromPath(f.path, f.family);\n return { ...f, registered, ...(registered ? {} : { message: 'Font registration returned no font.' }) };\n } catch (error) {\n return { ...f, registered: false, message: error instanceof Error ? error.message : String(error) };\n }\n });\n}\n\nfunction withFontFindings<R extends AnyRenderReport>(report: R, fonts: FontRegistration[]): R {\n const failed = fonts.filter((f) => !f.registered);\n if (!failed.length) return report;\n return {\n ...report,\n ok: false,\n diagnostics: [\n ...report.diagnostics.filter((d) => d.severity === 'error'),\n ...failed.map((f) => ({\n code: 'font-fallback', severity: 'warning' as const,\n message: `Could not register \"${f.family}\"; text may use an available fallback font.`,\n details: { family: f.family, path: f.path, reason: f.message },\n hint: 'Supply a readable font file and register it under the family used by the theme.',\n })),\n ...report.diagnostics.filter((d) => d.severity !== 'error'),\n ],\n };\n}\n\n/**\n * Render `spec` to a PNG buffer and its {@link RenderReport}, entirely in Node.\n *\n * Supports the entire catalog: line, area, bar, scatter, box, pie, heatmap,\n * sankey, choropleth, combo, histogram, funnel, and the formerly DOM-only kpi,\n * table, matrix, slicers and dashboard (painted as a static canvas snapshot).\n */\nexport function renderChart(spec: DashboardSpec, options?: RenderImageOptions): NodeRenderResult<DashboardRenderReport>;\nexport function renderChart(spec: ChartSpec, options?: RenderImageOptions): NodeRenderResult;\nexport function renderChart(spec: AnySpec, options?: RenderImageOptions): NodeRenderResult<AnyRenderReport>;\nexport function renderChart(spec: AnySpec, options: RenderImageOptions = {}): NodeRenderResult<AnyRenderReport> {\n const { width, height, dpr } = validateRenderImageOptions(options);\n\n const fontRegistrations = registerFonts(options.fonts);\n\n const backing = computeBackingSize(width, height, dpr);\n const pxW = backing.pixelWidth;\n const pxH = backing.pixelHeight;\n\n const canvas = createCanvas(pxW, pxH);\n const ctx = canvas.getContext('2d');\n ctx.scale(backing.dpr, backing.dpr);\n\n // A dedicated 1×1 context for text measurement so layout uses real font\n // metrics (not the SSR heuristic) without mutating the drawing context.\n const measureCtx = createCanvas(1, 1).getContext('2d');\n\n // The interaction layer is unused on the static path; give it its own canvas\n // so a stray hover-layer paint can never land on the marks canvas.\n const interactionCtx = createCanvas(pxW, pxH).getContext('2d');\n interactionCtx.scale(backing.dpr, backing.dpr);\n\n const restoreMeasureContext = setMeasureContext(measureCtx as unknown as CanvasRenderingContext2D);\n let report: AnyRenderReport;\n let effectiveSpec = spec;\n let quality: QualityResult | undefined;\n let warnings: ValidationError[] | undefined;\n try {\n const target = {\n marks: ctx as unknown as CanvasRenderingContext2D,\n interaction: interactionCtx as unknown as CanvasRenderingContext2D,\n width,\n height,\n backingStore: {\n requestedDpr: backing.requestedDpr,\n effectiveDpr: backing.dpr,\n pixelWidth: backing.pixelWidth,\n pixelHeight: backing.pixelHeight,\n clamped: backing.clamped,\n },\n };\n if (options.quality) {\n const selected = draft(spec, { target, repair: false, quality: options.quality });\n if (!selected.report) throw new NodeRenderError(selected);\n report = selected.report;\n effectiveSpec = selected.spec as AnySpec;\n quality = selected.quality;\n warnings = selected.warnings;\n } else {\n report = spec.type === 'dashboard'\n ? renderDashboardToContext(target, spec)\n : renderToContext(target, spec);\n }\n } finally {\n restoreMeasureContext();\n }\n\n if (quality) {\n for (const iteration of quality.iterations) {\n if (iteration.report) iteration.report = withFontFindings(iteration.report, fontRegistrations);\n }\n report = quality.iterations.find((i) => i.pass === quality.selectedPass)?.report ?? report;\n if (!report.ok && quality.stopReason === 'satisfied') quality.stopReason = 'no-actions';\n } else {\n report = withFontFindings(report, fontRegistrations);\n }\n return {\n png: canvas.toBuffer('image/png'), report, spec: effectiveSpec, quality,\n ...(warnings ? { warnings } : {}),\n fontRegistrations, width: pxW, height: pxH,\n };\n}\n\n/**\n * Convenience wrapper around {@link renderChart} that returns only the PNG bytes.\n */\nexport function renderToPNG(spec: AnySpec, options?: RenderImageOptions): Buffer {\n return renderChart(spec, options).png;\n}\n\n/** Re-exported so callers can register fonts globally without a second dep. */\nexport { GlobalFonts };\nexport type {\n AnySpec, ChartSpec, DashboardSpec, QualityOptions, QualityResult,\n AnyRenderReport, DashboardRenderReport, RenderReport,\n ValidationError,\n} from 'graphein';\n\nexport const VERSION = '0.21.0';\n"],"mappings":";;;;;AAoBA,SAAS,cAAc,mBAAmB;AAC1C;AAAA,EACE;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA+DA,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YAAqB,QAAqB;AACxC,WAAO,OAAO,eAAe,OAAO,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI,MACxF,gDAAgD;AAF/B;AAGnB,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAM,gBAAgB;AACtB,IAAM,iBAAiB;AACvB,IAAM,cAAc;AAGb,IAAM,gBAAgB,OAAO,OAAO;AAAA,EACzC,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,WAAW;AACb,CAAC;AAeD,SAAS,oBAAoB,MAAc,OAAe,KAAa,KAAa,MAAoB;AACtG,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,OAAO,QAAQ,KAAK;AACzD,UAAM,IAAI,WAAW,2BAA2B,IAAI,qCAAqC,GAAG,QAAQ,GAAG,GAAG,OAAO,IAAI,IAAI,KAAK,EAAE,GAAG;AAAA,EACrI;AACF;AAGO,SAAS,2BAA2B,UAA8B,CAAC,GAA+B;AACvG,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,MAAM,QAAQ,OAAO;AAC3B,sBAAoB,SAAS,OAAO,cAAc,UAAU,cAAc,UAAU,YAAY;AAChG,sBAAoB,UAAU,QAAQ,cAAc,WAAW,cAAc,WAAW,YAAY;AACpG,sBAAoB,OAAO,KAAK,cAAc,QAAQ,cAAc,QAAQ,EAAE;AAC9E,QAAM,aAAa,KAAK,MAAM,QAAQ,GAAG;AACzC,QAAM,cAAc,KAAK,MAAM,SAAS,GAAG;AAC3C,QAAM,SAAS,aAAa;AAC5B,MAAI,CAAC,OAAO,cAAc,MAAM,KAAK,SAAS,cAAc,WAAW;AACrE,UAAM,IAAI;AAAA,MACR,wBAAwB,UAAU,OAAI,WAAW,KAAK,OAAO,eAAe,OAAO,CAAC,wBAAwB,cAAc,UAAU,eAAe,OAAO,CAAC;AAAA,IAC7J;AAAA,EACF;AACA,SAAO,EAAE,OAAO,QAAQ,KAAK,YAAY,YAAY;AACvD;AAEA,SAAS,cAAc,OAAmD;AACxE,UAAQ,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM;AAC9B,QAAI;AACF,YAAM,aAAa,CAAC,CAAC,YAAY,iBAAiB,EAAE,MAAM,EAAE,MAAM;AAClE,aAAO,EAAE,GAAG,GAAG,YAAY,GAAI,aAAa,CAAC,IAAI,EAAE,SAAS,sCAAsC,EAAG;AAAA,IACvG,SAAS,OAAO;AACd,aAAO,EAAE,GAAG,GAAG,YAAY,OAAO,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AAAA,IACpG;AAAA,EACF,CAAC;AACH;AAEA,SAAS,iBAA4C,QAAW,OAA8B;AAC5F,QAAM,SAAS,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU;AAChD,MAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,SAAO;AAAA,IACL,GAAG;AAAA,IACH,IAAI;AAAA,IACJ,aAAa;AAAA,MACX,GAAG,OAAO,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO;AAAA,MAC1D,GAAG,OAAO,IAAI,CAAC,OAAO;AAAA,QACpB,MAAM;AAAA,QAAiB,UAAU;AAAA,QACjC,SAAS,uBAAuB,EAAE,MAAM;AAAA,QACxC,SAAS,EAAE,QAAQ,EAAE,QAAQ,MAAM,EAAE,MAAM,QAAQ,EAAE,QAAQ;AAAA,QAC7D,MAAM;AAAA,MACR,EAAE;AAAA,MACF,GAAG,OAAO,YAAY,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO;AAAA,IAC5D;AAAA,EACF;AACF;AAYO,SAAS,YAAY,MAAe,UAA8B,CAAC,GAAsC;AAC9G,QAAM,EAAE,OAAO,QAAQ,IAAI,IAAI,2BAA2B,OAAO;AAEjE,QAAM,oBAAoB,cAAc,QAAQ,KAAK;AAErD,QAAM,UAAU,mBAAmB,OAAO,QAAQ,GAAG;AACrD,QAAM,MAAM,QAAQ;AACpB,QAAM,MAAM,QAAQ;AAEpB,QAAM,SAAS,aAAa,KAAK,GAAG;AACpC,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,MAAM,QAAQ,KAAK,QAAQ,GAAG;AAIlC,QAAM,aAAa,aAAa,GAAG,CAAC,EAAE,WAAW,IAAI;AAIrD,QAAM,iBAAiB,aAAa,KAAK,GAAG,EAAE,WAAW,IAAI;AAC7D,iBAAe,MAAM,QAAQ,KAAK,QAAQ,GAAG;AAE7C,QAAM,wBAAwB,kBAAkB,UAAiD;AACjG,MAAI;AACJ,MAAI,gBAAgB;AACpB,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,UAAM,SAAS;AAAA,MACb,OAAO;AAAA,MACP,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA,cAAc;AAAA,QACZ,cAAc,QAAQ;AAAA,QACtB,cAAc,QAAQ;AAAA,QACtB,YAAY,QAAQ;AAAA,QACpB,aAAa,QAAQ;AAAA,QACrB,SAAS,QAAQ;AAAA,MACnB;AAAA,IACF;AACA,QAAI,QAAQ,SAAS;AACnB,YAAM,WAAW,MAAM,MAAM,EAAE,QAAQ,QAAQ,OAAO,SAAS,QAAQ,QAAQ,CAAC;AAChF,UAAI,CAAC,SAAS,OAAQ,OAAM,IAAI,gBAAgB,QAAQ;AACxD,eAAS,SAAS;AAClB,sBAAgB,SAAS;AACzB,gBAAU,SAAS;AACnB,iBAAW,SAAS;AAAA,IACtB,OAAO;AACL,eAAS,KAAK,SAAS,cACnB,yBAAyB,QAAQ,IAAI,IACrC,gBAAgB,QAAQ,IAAI;AAAA,IAClC;AAAA,EACF,UAAE;AACA,0BAAsB;AAAA,EACxB;AAEA,MAAI,SAAS;AACX,eAAW,aAAa,QAAQ,YAAY;AAC1C,UAAI,UAAU,OAAQ,WAAU,SAAS,iBAAiB,UAAU,QAAQ,iBAAiB;AAAA,IAC/F;AACA,aAAS,QAAQ,WAAW,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,YAAY,GAAG,UAAU;AACpF,QAAI,CAAC,OAAO,MAAM,QAAQ,eAAe,YAAa,SAAQ,aAAa;AAAA,EAC7E,OAAO;AACL,aAAS,iBAAiB,QAAQ,iBAAiB;AAAA,EACrD;AACA,SAAO;AAAA,IACL,KAAK,OAAO,SAAS,WAAW;AAAA,IAAG;AAAA,IAAQ,MAAM;AAAA,IAAe;AAAA,IAChE,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IAC/B;AAAA,IAAmB,OAAO;AAAA,IAAK,QAAQ;AAAA,EACzC;AACF;AAKO,SAAS,YAAY,MAAe,SAAsC;AAC/E,SAAO,YAAY,MAAM,OAAO,EAAE;AACpC;AAUO,IAAM,UAAU;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphein/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Headless Graphein rendering for Node — render any ChartSpec to a PNG buffer with a RenderReport, no browser required.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,6 +57,6 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@napi-rs/canvas": "^1.0.1",
|
|
60
|
-
"graphein": "^0.
|
|
60
|
+
"graphein": "^0.21.0"
|
|
61
61
|
}
|
|
62
62
|
}
|