@danielsimonjr/mathts-workbook 0.3.23 → 0.4.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.
@@ -4,6 +4,11 @@ var POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "proto
4
4
  function parseYamlHardened(content) {
5
5
  return parseYaml(content, { schema: "core", merge: false, logLevel: "silent" });
6
6
  }
7
+ function parseYamlSafe(content) {
8
+ const value = parseYamlHardened(content);
9
+ assertNoPollution(value);
10
+ return value;
11
+ }
7
12
  function isPlainObject(value) {
8
13
  return typeof value === "object" && value !== null && !Array.isArray(value);
9
14
  }
@@ -15,6 +20,10 @@ function findPollutionKeys(value, seen = /* @__PURE__ */ new WeakSet(), found =
15
20
  for (const item of value) findPollutionKeys(item, seen, found);
16
21
  return found;
17
22
  }
23
+ const proto = Object.getPrototypeOf(value);
24
+ if (proto !== Object.prototype && proto !== null) {
25
+ found.push("__proto__");
26
+ }
18
27
  for (const key of Object.getOwnPropertyNames(value)) {
19
28
  if (POLLUTION_KEYS.has(key)) {
20
29
  found.push(key);
@@ -172,7 +181,14 @@ var IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
172
181
  function isValidIdentifier(id) {
173
182
  return typeof id === "string" && IDENTIFIER_RE.test(id);
174
183
  }
175
- var SUPPORTED_CELL_TYPES = ["code", "markdown", "data", "test", "equation", "visualization"];
184
+ var SUPPORTED_CELL_TYPES = [
185
+ "code",
186
+ "markdown",
187
+ "data",
188
+ "test",
189
+ "equation",
190
+ "visualization"
191
+ ];
176
192
  var EXECUTION_MODES = ["reactive", "sequential", "manual"];
177
193
  function detectCellType(cell) {
178
194
  for (const key of CELL_TYPE_KEYS) {
@@ -210,9 +226,7 @@ function mapCell(raw, index, errors) {
210
226
  errors.push(`Cell at index ${index}: missing "id"`);
211
227
  id = `#${index}`;
212
228
  } else if (typeof rawId !== "string" || !IDENTIFIER_RE.test(rawId)) {
213
- errors.push(
214
- `Cell "${String(rawId)}": id must be a valid identifier ([A-Za-z_][A-Za-z0-9_]*)`
215
- );
229
+ errors.push(`Cell "${String(rawId)}": id must be a valid identifier ([A-Za-z_][A-Za-z0-9_]*)`);
216
230
  id = String(rawId);
217
231
  } else {
218
232
  id = rawId;
@@ -360,11 +374,18 @@ function importWorkbook(input) {
360
374
  try {
361
375
  obj = parseYamlHardened(input);
362
376
  } catch (error) {
363
- return { success: false, errors: [`Invalid JSON/YAML: ${error instanceof Error ? error.message : String(error)}`] };
377
+ return {
378
+ success: false,
379
+ errors: [`Invalid JSON/YAML: ${error instanceof Error ? error.message : String(error)}`]
380
+ };
364
381
  }
365
382
  if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
366
383
  return { success: false, errors: ['Input must be an object with a "cells" array'] };
367
384
  }
385
+ const pollution = findPollutionKeys(obj);
386
+ if (pollution.length > 0) {
387
+ return { success: false, errors: [`Disallowed key "${pollution[0]}" (prototype pollution)`] };
388
+ }
368
389
  const root = obj;
369
390
  if (!Array.isArray(root.cells)) {
370
391
  return { success: false, errors: ['Input must have a "cells" array'] };
@@ -556,8 +577,7 @@ var WorkbookExecutor = class {
556
577
  * parser, so both YAML entry points are consistent.
557
578
  */
558
579
  async executeData(cell) {
559
- const value = parseYamlHardened(cell.content);
560
- assertNoPollution(value);
580
+ const value = parseYamlSafe(cell.content);
561
581
  return value;
562
582
  }
563
583
  /**
@@ -699,7 +719,7 @@ function formatResult(value) {
699
719
  }
700
720
 
701
721
  export {
702
- parseYamlHardened,
722
+ parseYamlSafe,
703
723
  buildDependencyGraph,
704
724
  topologicalSort,
705
725
  getDependents,
@@ -8,7 +8,7 @@ import {
8
8
  isValidIdentifier,
9
9
  parseWorkbook,
10
10
  serializeWorkbook
11
- } from "./chunk-3KTM2DZC.js";
11
+ } from "./chunk-M2K7SQG2.js";
12
12
 
13
13
  // src/fs-atomic.ts
14
14
  import { writeFileSync, renameSync, unlinkSync } from "fs";
@@ -321,7 +321,7 @@ var Session = class {
321
321
 
322
322
  // src/contract.ts
323
323
  var SCHEMA_VERSION = { major: 1, minor: 0 };
324
- var VERSION = "0.3.23";
324
+ var VERSION = "0.4.0";
325
325
  var COMMAND_NAMES = [
326
326
  "run",
327
327
  "describe",
package/dist/cli.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  runWorkbookWithTimeout,
16
16
  setMetadata,
17
17
  writeFileAtomic
18
- } from "./chunk-UU537RJR.js";
18
+ } from "./chunk-ZVCYWKM5.js";
19
19
  import {
20
20
  buildDependencyGraph,
21
21
  createExecutor,
@@ -23,14 +23,15 @@ import {
23
23
  formatResult,
24
24
  importWorkbook,
25
25
  parseWorkbook,
26
- parseYamlHardened,
26
+ parseYamlSafe,
27
27
  serializeWorkbook,
28
28
  stripOutputs,
29
29
  toDOT,
30
30
  toMermaid
31
- } from "./chunk-3KTM2DZC.js";
31
+ } from "./chunk-M2K7SQG2.js";
32
32
 
33
33
  // src/cli.ts
34
+ import { createHash } from "crypto";
34
35
  import { readFileSync, writeFileSync, lstatSync, realpathSync } from "fs";
35
36
  import { pathToFileURL } from "url";
36
37
  import { basename } from "path";
@@ -599,6 +600,7 @@ mtsw - MathTS Workbook CLI
599
600
 
600
601
  Usage:
601
602
  mtsw run <file> [-c <id>] [-v] [--json] [--write] [--timeout <ms>]
603
+ [--expect-hash <sha256>]
602
604
  Execute a workbook (or one cell + its
603
605
  deps with -c/--cell). -v: events,
604
606
  --json: machine output, --write: persist
@@ -607,6 +609,8 @@ Usage:
607
609
  kills it (WorkbookTimeoutError) if it
608
610
  exceeds the budget \u2014 a runaway cell can't
609
611
  hang the process. Incompatible with -c/-v.
612
+ --expect-hash refuses to run unless the
613
+ file's SHA-256 matches (optimistic lock).
610
614
  mtsw describe <file> [--json] Structured document model (cells, graph)
611
615
  mtsw validate <file> [--json] Validate structure (ids, deps, cycles)
612
616
  mtsw graph <file> [-f mermaid|dot] Print the dependency graph
@@ -790,6 +794,13 @@ async function runCommand(args) {
790
794
  }
791
795
  const read = readFile(file);
792
796
  if (read.error) return fail([read.error]);
797
+ const expectHash = flagValue(args, "--expect-hash");
798
+ if (expectHash !== void 0) {
799
+ const actual = createHash("sha256").update(read.content, "utf8").digest("hex");
800
+ if (actual.toLowerCase() !== expectHash.toLowerCase()) {
801
+ return fail([`--expect-hash mismatch: file SHA-256 is ${actual}, expected ${expectHash}`]);
802
+ }
803
+ }
793
804
  const parsed = parseWorkbook(read.content);
794
805
  if (!parsed.success) {
795
806
  const problems2 = parsed.errors ?? [];
@@ -877,7 +888,7 @@ function validateChartSpecs(workbook) {
877
888
  if (cell.type !== "visualization") continue;
878
889
  let spec;
879
890
  try {
880
- spec = parseYamlHardened(cell.content);
891
+ spec = parseYamlSafe(cell.content);
881
892
  } catch (error) {
882
893
  problems.push(`Cell "${cell.id}": invalid chart spec (${errMessage(error)})`);
883
894
  continue;
@@ -1087,7 +1098,7 @@ function buildRenderDoc(workbook, byId, format = "svg") {
1087
1098
  if (c.type === "visualization") {
1088
1099
  rc.type = "chart";
1089
1100
  try {
1090
- const spec = parseYamlHardened(c.content);
1101
+ const spec = parseYamlSafe(c.content);
1091
1102
  const rendered = renderChart(
1092
1103
  { type: spec?.type, title: spec?.title, xLabel: spec?.x?.label, yLabel: spec?.y?.label },
1093
1104
  lookup(spec?.x?.data),
package/dist/index.d.ts CHANGED
@@ -431,6 +431,25 @@ interface HandleResult {
431
431
  }
432
432
  declare function handleRequest(session: Session, request: JsonRpcRequest): Promise<HandleResult>;
433
433
 
434
+ /**
435
+ * MathML → SVG typesetting. Wraps a MathML fragment in an SVG document so
436
+ * rendered workbooks can emit SVG (browsers, rsvg, inkscape) instead of raw
437
+ * MathML. The glyph metrics are a conservative estimate — consumers that need
438
+ * pixel-perfect layout should typeset with a real engine; this is the
439
+ * no-dependency path the CLI/export uses.
440
+ */
441
+ interface MathSvgOptions {
442
+ /** Display (`block`) or inline. Default `block`. */
443
+ display?: 'block' | 'inline';
444
+ /** Extra padding in CSS pixels. Default 4. */
445
+ padding?: number;
446
+ }
447
+ /**
448
+ * Wrap a MathML fragment (or a full `<math>` document) in an SVG with a
449
+ * `foreignObject` so the math remains selectable and scalable.
450
+ */
451
+ declare function mathMLToSVG(mathml: string, options?: MathSvgOptions): string;
452
+
434
453
  /**
435
454
  * Shared message/result shapes between `timeout-runner.ts` (the coordinator,
436
455
  * running on the caller's thread) and `run-worker.ts` (the `worker_threads`
@@ -480,4 +499,4 @@ declare class WorkbookTimeoutError extends Error {
480
499
  */
481
500
  declare function runWorkbookWithTimeout(source: string, options: RunWorkbookWithTimeoutOptions): Promise<SerializedRunResult>;
482
501
 
483
- export { type Cell, type CellPosition, type CellResult, type CellType, type ExecutionMode, type ParseResult, type RemoveResult, type RunResult, type RunWorkbookWithTimeoutOptions, type RuntimeConfig, SCHEMA_VERSION, type SerializedCellResult, type SerializedRunResult, Session, VERSION, type Workbook, type WorkbookEvent, WorkbookExecutor, type WorkbookMetadata, WorkbookTimeoutError, addCell, buildDependencyGraph, createExecutor, detectCellType, detectCycles, editCell, formatResult, getAncestors, getDependents, handleRequest, moveCell, parseWorkbook, removeCell, renameCell, runWorkbookWithTimeout, serializeWorkbook, setMetadata, stripOutputs, toMermaid, topologicalSort };
502
+ export { type Cell, type CellPosition, type CellResult, type CellType, type ExecutionMode, type MathSvgOptions, type ParseResult, type RemoveResult, type RunResult, type RunWorkbookWithTimeoutOptions, type RuntimeConfig, SCHEMA_VERSION, type SerializedCellResult, type SerializedRunResult, Session, VERSION, type Workbook, type WorkbookEvent, WorkbookExecutor, type WorkbookMetadata, WorkbookTimeoutError, addCell, buildDependencyGraph, createExecutor, detectCellType, detectCycles, editCell, formatResult, getAncestors, getDependents, handleRequest, mathMLToSVG, moveCell, parseWorkbook, removeCell, renameCell, runWorkbookWithTimeout, serializeWorkbook, setMetadata, stripOutputs, toMermaid, topologicalSort };
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  renameCell,
12
12
  runWorkbookWithTimeout,
13
13
  setMetadata
14
- } from "./chunk-UU537RJR.js";
14
+ } from "./chunk-ZVCYWKM5.js";
15
15
  import {
16
16
  WorkbookExecutor,
17
17
  buildDependencyGraph,
@@ -26,7 +26,24 @@ import {
26
26
  stripOutputs,
27
27
  toMermaid,
28
28
  topologicalSort
29
- } from "./chunk-3KTM2DZC.js";
29
+ } from "./chunk-M2K7SQG2.js";
30
+
31
+ // src/svg-math.ts
32
+ function mathMLToSVG(mathml, options = {}) {
33
+ const display = options.display ?? "block";
34
+ const pad = options.padding ?? 4;
35
+ const inner = mathml.includes("<math") ? mathml : `<math xmlns="http://www.w3.org/1998/Math/MathML" display="${display}">${mathml}</math>`;
36
+ const text = inner.replace(/<[^>]+>/g, "");
37
+ const width = Math.max(24, text.length * 10 + pad * 2);
38
+ const height = display === "block" ? 36 + pad * 2 : 20 + pad * 2;
39
+ return [
40
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`,
41
+ `<foreignObject x="${pad}" y="${pad}" width="${width - pad * 2}" height="${height - pad * 2}">`,
42
+ inner,
43
+ `</foreignObject>`,
44
+ `</svg>`
45
+ ].join("");
46
+ }
30
47
  export {
31
48
  SCHEMA_VERSION,
32
49
  Session,
@@ -43,6 +60,7 @@ export {
43
60
  getAncestors,
44
61
  getDependents,
45
62
  handleRequest,
63
+ mathMLToSVG,
46
64
  moveCell,
47
65
  parseWorkbook,
48
66
  removeCell,
@@ -2,7 +2,7 @@ import {
2
2
  createExecutor,
3
3
  formatResult,
4
4
  parseWorkbook
5
- } from "./chunk-3KTM2DZC.js";
5
+ } from "./chunk-M2K7SQG2.js";
6
6
 
7
7
  // src/run-worker.ts
8
8
  import { parentPort, workerData } from "worker_threads";
package/package.json CHANGED
@@ -1,64 +1,64 @@
1
- {
2
- "name": "@danielsimonjr/mathts-workbook",
3
- "version": "0.3.23",
4
- "description": "Scientific workbook runtime for MathTS (.mtsw format)",
5
- "author": "Daniel Simon Jr.",
6
- "license": "MIT",
7
- "type": "module",
8
- "main": "./dist/index.js",
9
- "module": "./dist/index.js",
10
- "types": "./dist/index.d.ts",
11
- "bin": {
12
- "mtsw": "dist/cli.js"
13
- },
14
- "exports": {
15
- ".": {
16
- "import": "./dist/index.js",
17
- "types": "./dist/index.d.ts"
18
- }
19
- },
20
- "files": [
21
- "dist",
22
- "README.md"
23
- ],
24
- "scripts": {
25
- "build": "tsup",
26
- "dev": "tsup --watch",
27
- "test": "vitest run",
28
- "test:watch": "vitest",
29
- "test:coverage": "vitest run --coverage",
30
- "typecheck": "tsc --noEmit",
31
- "lint": "eslint src --ext .ts",
32
- "lint:fix": "eslint src --ext .ts --fix",
33
- "clean": "rm -rf dist",
34
- "build:prod": "tsup --minify --treeshake"
35
- },
36
- "dependencies": {
37
- "@danielsimonjr/mathts-core": "^0.14.0",
38
- "@danielsimonjr/mathts-expression": "^0.7.0",
39
- "@danielsimonjr/mathts-functions": "^0.62.0",
40
- "@danielsimonjr/mathts-plot": "^0.4.0",
41
- "yaml": "^2.3.0"
42
- },
43
- "devDependencies": {
44
- "@types/node": "^25.5.2",
45
- "tsup": "^8.0.0",
46
- "typescript": "^5.3.0",
47
- "vitest": "^4.1.5"
48
- },
49
- "publishConfig": {
50
- "access": "public"
51
- },
52
- "repository": {
53
- "type": "git",
54
- "url": "git+https://github.com/danielsimonjr/MathTS.git",
55
- "directory": "workbook"
56
- },
57
- "keywords": [
58
- "notebook",
59
- "workbook",
60
- "scientific-computing",
61
- "reactive",
62
- "yaml"
63
- ]
64
- }
1
+ {
2
+ "name": "@danielsimonjr/mathts-workbook",
3
+ "version": "0.4.0",
4
+ "description": "Scientific workbook runtime for MathTS (.mtsw format)",
5
+ "author": "Daniel Simon Jr.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "bin": {
12
+ "mtsw": "dist/cli.js"
13
+ },
14
+ "exports": {
15
+ ".": {
16
+ "import": "./dist/index.js",
17
+ "types": "./dist/index.d.ts"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "dev": "tsup --watch",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "test:coverage": "vitest run --coverage",
30
+ "typecheck": "tsc --noEmit",
31
+ "lint": "eslint src --ext .ts",
32
+ "lint:fix": "eslint src --ext .ts --fix",
33
+ "clean": "rm -rf dist",
34
+ "build:prod": "tsup --minify --treeshake"
35
+ },
36
+ "dependencies": {
37
+ "@danielsimonjr/mathts-core": "^0.14.1",
38
+ "@danielsimonjr/mathts-expression": "^0.7.1",
39
+ "@danielsimonjr/mathts-functions": "^0.64.0",
40
+ "@danielsimonjr/mathts-plot": "^0.4.2",
41
+ "yaml": "^2.3.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^26.2.0",
45
+ "tsup": "^8.0.0",
46
+ "typescript": "^5.3.0",
47
+ "vitest": "^4.1.10"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/danielsimonjr/MathTS.git",
55
+ "directory": "workbook"
56
+ },
57
+ "keywords": [
58
+ "notebook",
59
+ "workbook",
60
+ "scientific-computing",
61
+ "reactive",
62
+ "yaml"
63
+ ]
64
+ }