@danielsimonjr/mathts-workbook 0.2.0 → 0.3.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/README.md +33 -8
- package/dist/{chunk-TS2WDJ7W.js → chunk-3KTM2DZC.js} +2 -521
- package/dist/chunk-TMVFG3I3.js +581 -0
- package/dist/cli.js +131 -23
- package/dist/index.d.ts +50 -1
- package/dist/index.js +14 -8
- package/dist/run-worker.d.ts +2 -0
- package/dist/run-worker.js +39 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ mtsw run example.mtsw
|
|
|
19
19
|
mtsw run example.mtsw -v # also print the execution event stream
|
|
20
20
|
mtsw run example.mtsw --json # machine-readable envelope on stdout
|
|
21
21
|
mtsw run example.mtsw -c gauss # run one cell + its transitive deps (stateless)
|
|
22
|
+
mtsw run example.mtsw --timeout 5000 # run in a worker; killed (exit 1) if it exceeds 5s
|
|
22
23
|
|
|
23
24
|
# Describe the structured document model (cells, outputs, dependency graph).
|
|
24
25
|
mtsw describe example.mtsw --json
|
|
@@ -73,6 +74,9 @@ mtsw export example.mtsw -o example.html # one offline file; stdout if no -o
|
|
|
73
74
|
mtsw export example.mtsw --no-run # render cached outputs without executing
|
|
74
75
|
mtsw export example.mtsw --json # envelope: { data: { path, bytes } }
|
|
75
76
|
|
|
77
|
+
# Other export formats: --format tex|pdf|json|ipynb (html is the default).
|
|
78
|
+
mtsw export example.mtsw --format ipynb -o example.ipynb # Jupyter notebook (nbformat v4)
|
|
79
|
+
|
|
76
80
|
# Persistent session for a GUI/tooling: JSON-RPC 2.0 over stdio (NDJSON).
|
|
77
81
|
mtsw serve
|
|
78
82
|
# -> {"jsonrpc":"2.0","id":1,"method":"open","params":{"path":"example.mtsw"}}
|
|
@@ -94,9 +98,30 @@ dependencies**. Equation cells contain MathTS expression syntax (e.g.
|
|
|
94
98
|
outputs); a whole-run failure such as a dependency cycle fails loudly rather than
|
|
95
99
|
emitting a misleading document.
|
|
96
100
|
|
|
101
|
+
**`export --format ipynb` (Jupyter notebook).** Renders the same run report to a
|
|
102
|
+
structurally conformant **nbformat v4** JSON document: `markdown` cells map to
|
|
103
|
+
notebook markdown cells; every other cell type (`code`, `equation`, `test`, `data`,
|
|
104
|
+
`visualization`) maps to a notebook code cell, with a computed result becoming an
|
|
105
|
+
`execute_result` output (`data['text/plain']`), an error becoming an `error` output,
|
|
106
|
+
and a chart becoming a `display_data` output (inline SVG). Shares the same
|
|
107
|
+
run-then-render pipeline as `--format html`/`tex` (including `--no-run`, `--json`,
|
|
108
|
+
`-o`).
|
|
109
|
+
|
|
110
|
+
**`run --timeout <ms>` (kill-able worker-thread run).** By default, cell execution runs
|
|
111
|
+
in-process with no time budget — a runaway cell (e.g. an unbounded computation) hangs
|
|
112
|
+
the process. `--timeout` runs the whole workbook in a `worker_threads` Worker and
|
|
113
|
+
forcibly terminates it if it exceeds the budget, exiting 1 with a clear
|
|
114
|
+
`workbook execution exceeded <ms>ms and was terminated` message; termination kills the
|
|
115
|
+
worker outright, so it interrupts even a synchronous, CPU-bound runaway. It always runs
|
|
116
|
+
the entire workbook to completion-or-termination, so it's incompatible with `-c`/`-v`.
|
|
117
|
+
The same primitive is available programmatically as `runWorkbookWithTimeout(source, {
|
|
118
|
+
timeoutMs })` (throws `WorkbookTimeoutError` on timeout); cell outputs come back
|
|
119
|
+
pre-formatted to strings (via `formatResult`), since engine class instances (Complex,
|
|
120
|
+
matrices, …) don't survive the worker's `postMessage`.
|
|
121
|
+
|
|
97
122
|
**`serve` (persistent session).** One long-lived process holds the workbook in memory with a per-cell result cache and a **stale set**: a `cell.*` edit marks that cell and its transitive dependents stale, and a `run` re-executes **only** the stale cells (reusing cached outputs for the rest) — the incremental latency win a GUI needs. Requests are processed strictly in order; `run` streams `cell/event` notifications (flushed before that run's response in v1, not mid-run). Edits stay in memory until `save`. Single-document per process; concurrent writers are last-write-wins.
|
|
98
123
|
|
|
99
|
-
**Editing notes.** Cell edits are validity-preserving: an op that would create a duplicate/invalid id, a missing dependency, or a **dependency cycle** is rejected and the file is left byte-for-byte unchanged. Editing a cell clears its (now-stale) persisted output; `--at N` is the cell's final 0-based index; `--force` detaches dependents (clearing their outputs) but does not rewrite cell
|
|
124
|
+
**Editing notes.** Cell edits are validity-preserving: an op that would create a duplicate/invalid id, a missing dependency, or a **dependency cycle** is rejected and the file is left byte-for-byte unchanged. Editing a cell clears its (now-stale) persisted output; `--at N` is the cell's final 0-based index; `--force` detaches dependents (clearing their outputs) but does not rewrite cell _content_, so a dependent that still references the removed id by name will error at run. Concurrent editors are last-write-wins (an optimistic-lock guard arrives with the `serve` session).
|
|
100
125
|
|
|
101
126
|
Diagnostics and errors are written to stderr; results (including `--json`) go to stdout, so the exit code can be used in scripts independently of the output.
|
|
102
127
|
|
|
@@ -199,13 +224,13 @@ A `test` cell's expression must evaluate to a **boolean**: `true` passes, `false
|
|
|
199
224
|
|
|
200
225
|
## Cell types (v1)
|
|
201
226
|
|
|
202
|
-
| Type
|
|
203
|
-
|
|
|
204
|
-
| `markdown`
|
|
205
|
-
| `code`
|
|
206
|
-
| `data`
|
|
207
|
-
| `test`
|
|
208
|
-
| `tensor` / `equation` / `visualization` / `export` | ⏳
|
|
227
|
+
| Type | Status | Description |
|
|
228
|
+
| -------------------------------------------------- | ------ | ------------------------------------------------------ |
|
|
229
|
+
| `markdown` | ✅ | Documentation (passed through verbatim) |
|
|
230
|
+
| `code` | ✅ | MathTS expression script; last value is the result |
|
|
231
|
+
| `data` | ✅ | Structured YAML, parsed (hardened) into a value |
|
|
232
|
+
| `test` | ✅ | Boolean assertion (`true` = pass) |
|
|
233
|
+
| `tensor` / `equation` / `visualization` / `export` | ⏳ | Reserved; not executed in v1 (reported as unsupported) |
|
|
209
234
|
|
|
210
235
|
## Execution modes
|
|
211
236
|
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
// src/fs-atomic.ts
|
|
2
|
-
import { writeFileSync, renameSync, unlinkSync } from "fs";
|
|
3
|
-
var counter = 0;
|
|
4
|
-
function writeFileAtomic(file, content) {
|
|
5
|
-
const tmp = `${file}.${process.pid}.${counter++}.tmp`;
|
|
6
|
-
writeFileSync(tmp, content, { encoding: "utf-8", flag: "wx" });
|
|
7
|
-
try {
|
|
8
|
-
renameSync(tmp, file);
|
|
9
|
-
} catch (error) {
|
|
10
|
-
try {
|
|
11
|
-
unlinkSync(tmp);
|
|
12
|
-
} catch {
|
|
13
|
-
}
|
|
14
|
-
throw error;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
1
|
// src/yaml-safe.ts
|
|
19
2
|
import { parse as parseYaml } from "yaml";
|
|
20
3
|
var POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
@@ -653,496 +636,6 @@ function createExecutor(workbook) {
|
|
|
653
636
|
return new WorkbookExecutor(workbook);
|
|
654
637
|
}
|
|
655
638
|
|
|
656
|
-
// src/edit.ts
|
|
657
|
-
function cloneWorkbook(wb) {
|
|
658
|
-
return {
|
|
659
|
-
...wb,
|
|
660
|
-
metadata: { ...wb.metadata },
|
|
661
|
-
runtime: { ...wb.runtime },
|
|
662
|
-
cells: wb.cells.map((c) => ({
|
|
663
|
-
...c,
|
|
664
|
-
...c.dependsOn ? { dependsOn: [...c.dependsOn] } : {},
|
|
665
|
-
...c.metadata ? { metadata: { ...c.metadata } } : {}
|
|
666
|
-
}))
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
function assertSupportedType(type) {
|
|
670
|
-
if (!SUPPORTED_CELL_TYPES.includes(type)) {
|
|
671
|
-
throw new Error(`Unsupported cell type '${type}' (supported: ${SUPPORTED_CELL_TYPES.join(", ")})`);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
function indexOfCell(wb, id) {
|
|
675
|
-
const i = wb.cells.findIndex((c) => c.id === id);
|
|
676
|
-
if (i < 0) throw new Error(`No such cell: '${id}'`);
|
|
677
|
-
return i;
|
|
678
|
-
}
|
|
679
|
-
function validateDeps(wb, dependsOn, selfId) {
|
|
680
|
-
const ids = new Set(wb.cells.map((c) => c.id));
|
|
681
|
-
for (const dep of dependsOn) {
|
|
682
|
-
if (dep === selfId) throw new Error(`Cell '${selfId}' cannot depend on itself`);
|
|
683
|
-
if (!ids.has(dep)) {
|
|
684
|
-
throw new Error(`depends_on references unknown cell '${dep}'`);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
return dependsOn;
|
|
688
|
-
}
|
|
689
|
-
function assertNoNewCycle(before, after) {
|
|
690
|
-
if (detectCycles(buildDependencyGraph(before.cells)).length > 0) return;
|
|
691
|
-
const cycles = detectCycles(buildDependencyGraph(after.cells));
|
|
692
|
-
if (cycles.length > 0) {
|
|
693
|
-
throw new Error(
|
|
694
|
-
`Operation would create a dependency cycle: ${cycles.map((c) => c.join(" -> ")).join("; ")}`
|
|
695
|
-
);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
function clearResult(cell) {
|
|
699
|
-
delete cell.output;
|
|
700
|
-
delete cell.error;
|
|
701
|
-
}
|
|
702
|
-
function resolveInsertIndex(wb, position) {
|
|
703
|
-
if (!position) return wb.cells.length;
|
|
704
|
-
if (position.at !== void 0) {
|
|
705
|
-
if (!Number.isInteger(position.at) || position.at < 0 || position.at > wb.cells.length) {
|
|
706
|
-
throw new Error(`--at ${position.at} out of range (0..${wb.cells.length})`);
|
|
707
|
-
}
|
|
708
|
-
return position.at;
|
|
709
|
-
}
|
|
710
|
-
if (position.before !== void 0) {
|
|
711
|
-
const i = wb.cells.findIndex((c) => c.id === position.before);
|
|
712
|
-
if (i < 0) throw new Error(`--before: no such cell '${position.before}'`);
|
|
713
|
-
return i;
|
|
714
|
-
}
|
|
715
|
-
if (position.after !== void 0) {
|
|
716
|
-
const i = wb.cells.findIndex((c) => c.id === position.after);
|
|
717
|
-
if (i < 0) throw new Error(`--after: no such cell '${position.after}'`);
|
|
718
|
-
return i + 1;
|
|
719
|
-
}
|
|
720
|
-
return wb.cells.length;
|
|
721
|
-
}
|
|
722
|
-
function addCell(wb, spec, position) {
|
|
723
|
-
if (!isValidIdentifier(spec.id)) {
|
|
724
|
-
throw new Error(`Invalid cell id '${spec.id}': must be an identifier ([A-Za-z_][A-Za-z0-9_]*)`);
|
|
725
|
-
}
|
|
726
|
-
if (wb.cells.some((c) => c.id === spec.id)) {
|
|
727
|
-
throw new Error(`Duplicate cell id '${spec.id}'`);
|
|
728
|
-
}
|
|
729
|
-
assertSupportedType(spec.type);
|
|
730
|
-
const next = cloneWorkbook(wb);
|
|
731
|
-
const cell = { id: spec.id, type: spec.type, content: spec.content ?? "" };
|
|
732
|
-
const deps = spec.dependsOn ? validateDeps(next, spec.dependsOn, spec.id) : void 0;
|
|
733
|
-
if (deps && deps.length > 0) cell.dependsOn = deps;
|
|
734
|
-
next.cells.splice(resolveInsertIndex(next, position), 0, cell);
|
|
735
|
-
assertNoNewCycle(wb, next);
|
|
736
|
-
return next;
|
|
737
|
-
}
|
|
738
|
-
function editCell(wb, id, changes) {
|
|
739
|
-
const next = cloneWorkbook(wb);
|
|
740
|
-
const cell = next.cells[indexOfCell(next, id)];
|
|
741
|
-
let touched = false;
|
|
742
|
-
if (changes.type !== void 0) {
|
|
743
|
-
assertSupportedType(changes.type);
|
|
744
|
-
cell.type = changes.type;
|
|
745
|
-
touched = true;
|
|
746
|
-
}
|
|
747
|
-
if (changes.content !== void 0) {
|
|
748
|
-
cell.content = changes.content;
|
|
749
|
-
touched = true;
|
|
750
|
-
}
|
|
751
|
-
if (changes.dependsOn !== void 0) {
|
|
752
|
-
const deps = validateDeps(next, changes.dependsOn, id);
|
|
753
|
-
if (deps.length > 0) cell.dependsOn = deps;
|
|
754
|
-
else delete cell.dependsOn;
|
|
755
|
-
touched = true;
|
|
756
|
-
}
|
|
757
|
-
if (touched) clearResult(cell);
|
|
758
|
-
if (changes.dependsOn !== void 0) assertNoNewCycle(wb, next);
|
|
759
|
-
return next;
|
|
760
|
-
}
|
|
761
|
-
function removeCell(wb, id, options) {
|
|
762
|
-
const next = cloneWorkbook(wb);
|
|
763
|
-
indexOfCell(next, id);
|
|
764
|
-
const dependents = next.cells.filter((c) => (c.dependsOn ?? []).includes(id)).map((c) => c.id);
|
|
765
|
-
if (dependents.length > 0 && !options?.force) {
|
|
766
|
-
throw new Error(`Cell '${id}' has dependents: ${dependents.join(", ")} (use --force to remove and detach)`);
|
|
767
|
-
}
|
|
768
|
-
const changedCells = [];
|
|
769
|
-
for (const c of next.cells) {
|
|
770
|
-
if (c.dependsOn?.includes(id)) {
|
|
771
|
-
c.dependsOn = c.dependsOn.filter((d) => d !== id);
|
|
772
|
-
if (c.dependsOn.length === 0) delete c.dependsOn;
|
|
773
|
-
clearResult(c);
|
|
774
|
-
changedCells.push(c.id);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
next.cells = next.cells.filter((c) => c.id !== id);
|
|
778
|
-
return { workbook: next, changedCells };
|
|
779
|
-
}
|
|
780
|
-
function moveCell(wb, id, position) {
|
|
781
|
-
if (position.before === id || position.after === id) return cloneWorkbook(wb);
|
|
782
|
-
const next = cloneWorkbook(wb);
|
|
783
|
-
const from = indexOfCell(next, id);
|
|
784
|
-
const [cell] = next.cells.splice(from, 1);
|
|
785
|
-
next.cells.splice(resolveInsertIndex(next, position), 0, cell);
|
|
786
|
-
return next;
|
|
787
|
-
}
|
|
788
|
-
function renameCell(wb, oldId, newId) {
|
|
789
|
-
if (oldId === newId) return cloneWorkbook(wb);
|
|
790
|
-
const next = cloneWorkbook(wb);
|
|
791
|
-
const cell = next.cells[indexOfCell(next, oldId)];
|
|
792
|
-
if (!isValidIdentifier(newId)) {
|
|
793
|
-
throw new Error(`Invalid cell id '${newId}': must be an identifier ([A-Za-z_][A-Za-z0-9_]*)`);
|
|
794
|
-
}
|
|
795
|
-
if (next.cells.some((c) => c.id === newId)) {
|
|
796
|
-
throw new Error(`Duplicate cell id '${newId}'`);
|
|
797
|
-
}
|
|
798
|
-
cell.id = newId;
|
|
799
|
-
const reference = new RegExp(`\\b${oldId}\\b`, "g");
|
|
800
|
-
for (const c of next.cells) {
|
|
801
|
-
if (c.dependsOn?.includes(oldId)) {
|
|
802
|
-
c.dependsOn = c.dependsOn.map((d) => d === oldId ? newId : d);
|
|
803
|
-
c.content = c.content.replace(reference, newId);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
return next;
|
|
807
|
-
}
|
|
808
|
-
function setMetadata(wb, changes) {
|
|
809
|
-
const next = cloneWorkbook(wb);
|
|
810
|
-
if (changes.title !== void 0) next.metadata.title = changes.title;
|
|
811
|
-
if (changes.author !== void 0) next.metadata.author = changes.author;
|
|
812
|
-
if (changes.description !== void 0) next.metadata.description = changes.description;
|
|
813
|
-
if (changes.tags !== void 0) next.metadata.tags = changes.tags;
|
|
814
|
-
return next;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// src/session.ts
|
|
818
|
-
import { readFileSync } from "fs";
|
|
819
|
-
function canonicalCell(cell) {
|
|
820
|
-
const meta = cell.metadata ?? {};
|
|
821
|
-
const sortedMeta = {};
|
|
822
|
-
for (const key of Object.keys(meta).sort()) sortedMeta[key] = meta[key];
|
|
823
|
-
return JSON.stringify([cell.type, cell.content, cell.dependsOn ?? [], sortedMeta]);
|
|
824
|
-
}
|
|
825
|
-
var Session = class {
|
|
826
|
-
workbook = null;
|
|
827
|
-
path = null;
|
|
828
|
-
dirty = false;
|
|
829
|
-
cache = /* @__PURE__ */ new Map();
|
|
830
|
-
stale = /* @__PURE__ */ new Set();
|
|
831
|
-
require() {
|
|
832
|
-
if (!this.workbook) throw new Error("No workbook open");
|
|
833
|
-
return this.workbook;
|
|
834
|
-
}
|
|
835
|
-
/** A cell has a usable, up-to-date result (the basis for "not stale"). */
|
|
836
|
-
hasValidResult(id) {
|
|
837
|
-
const r = this.cache.get(id);
|
|
838
|
-
return r !== void 0 && (r.status === "success" || r.status === "pass");
|
|
839
|
-
}
|
|
840
|
-
/** Load a workbook file into the session. Refuses to discard unsaved edits. */
|
|
841
|
-
open(path, options) {
|
|
842
|
-
if (this.dirty && !options?.force) {
|
|
843
|
-
throw new Error("Workbook has unsaved changes (open with force to discard)");
|
|
844
|
-
}
|
|
845
|
-
const content = readFileSync(path, "utf-8");
|
|
846
|
-
const parsed = parseWorkbook(content);
|
|
847
|
-
if (!parsed.success) {
|
|
848
|
-
throw new Error(`Parse errors: ${(parsed.errors ?? []).join("; ")}`);
|
|
849
|
-
}
|
|
850
|
-
this.workbook = parsed.workbook;
|
|
851
|
-
this.path = path;
|
|
852
|
-
this.cache.clear();
|
|
853
|
-
this.stale = new Set(this.workbook.cells.map((c) => c.id));
|
|
854
|
-
this.dirty = false;
|
|
855
|
-
}
|
|
856
|
-
/** Replace the workbook and recompute the stale set by diffing old vs new. */
|
|
857
|
-
commit(next) {
|
|
858
|
-
const old = this.require();
|
|
859
|
-
const oldCanonical = new Map(old.cells.map((c) => [c.id, canonicalCell(c)]));
|
|
860
|
-
const changed = /* @__PURE__ */ new Set();
|
|
861
|
-
for (const cell of next.cells) {
|
|
862
|
-
const prev = oldCanonical.get(cell.id);
|
|
863
|
-
if (prev === void 0 || prev !== canonicalCell(cell)) changed.add(cell.id);
|
|
864
|
-
}
|
|
865
|
-
const graph = buildDependencyGraph(next.cells);
|
|
866
|
-
const affected = new Set(changed);
|
|
867
|
-
for (const id of changed) {
|
|
868
|
-
for (const dep of getDependents(graph, id)) affected.add(dep);
|
|
869
|
-
}
|
|
870
|
-
const survivingIds = new Set(next.cells.map((c) => c.id));
|
|
871
|
-
for (const id of [...this.cache.keys()]) {
|
|
872
|
-
if (!survivingIds.has(id) || affected.has(id)) this.cache.delete(id);
|
|
873
|
-
}
|
|
874
|
-
this.stale = new Set(next.cells.map((c) => c.id).filter((id) => !this.hasValidResult(id)));
|
|
875
|
-
this.workbook = next;
|
|
876
|
-
this.dirty = true;
|
|
877
|
-
}
|
|
878
|
-
// --- Edits (each is atomic: the pure op throws before any state change) ---
|
|
879
|
-
addCell(spec, position) {
|
|
880
|
-
this.commit(addCell(this.require(), spec, position));
|
|
881
|
-
}
|
|
882
|
-
editCell(id, changes) {
|
|
883
|
-
this.commit(editCell(this.require(), id, changes));
|
|
884
|
-
}
|
|
885
|
-
removeCell(id, options) {
|
|
886
|
-
const result = removeCell(this.require(), id, options);
|
|
887
|
-
this.commit(result.workbook);
|
|
888
|
-
return result.changedCells;
|
|
889
|
-
}
|
|
890
|
-
moveCell(id, position) {
|
|
891
|
-
this.commit(moveCell(this.require(), id, position));
|
|
892
|
-
}
|
|
893
|
-
renameCell(oldId, newId) {
|
|
894
|
-
this.commit(renameCell(this.require(), oldId, newId));
|
|
895
|
-
}
|
|
896
|
-
setMetadata(changes) {
|
|
897
|
-
this.commit(setMetadata(this.require(), changes));
|
|
898
|
-
}
|
|
899
|
-
/**
|
|
900
|
-
* Incremental run: execute only stale cells in the target set (`only` + its
|
|
901
|
-
* ancestors, or the whole workbook), reusing cached outputs for the rest.
|
|
902
|
-
* Returns the merged report over the target plus the streamed events.
|
|
903
|
-
*/
|
|
904
|
-
async run(only) {
|
|
905
|
-
const wb = this.require();
|
|
906
|
-
const graph = buildDependencyGraph(wb.cells);
|
|
907
|
-
const target = only ? new Set(getAncestors(graph, only)) : new Set(wb.cells.map((c) => c.id));
|
|
908
|
-
const toRun = new Set([...this.stale].filter((id) => target.has(id)));
|
|
909
|
-
const executor = new WorkbookExecutor(wb);
|
|
910
|
-
const seed = /* @__PURE__ */ new Map();
|
|
911
|
-
for (const [id, res] of this.cache) {
|
|
912
|
-
if (res.status === "success" || res.status === "pass") {
|
|
913
|
-
seed.set(id, res.output);
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
executor.seedOutputs(seed);
|
|
917
|
-
const events = [];
|
|
918
|
-
executor.on((e) => events.push({ type: e.type, cellId: e.cellId, error: e.error }));
|
|
919
|
-
const report = await executor.runReport({ only, runIds: toRun });
|
|
920
|
-
if (!report.ok && report.cells.some((c) => c.id === "(workbook)")) {
|
|
921
|
-
return { result: report, events };
|
|
922
|
-
}
|
|
923
|
-
for (const cr of report.cells) {
|
|
924
|
-
this.cache.set(cr.id, cr);
|
|
925
|
-
if (cr.status === "success" || cr.status === "pass") this.stale.delete(cr.id);
|
|
926
|
-
}
|
|
927
|
-
const ordered = graph.executionOrder.filter((id) => target.has(id));
|
|
928
|
-
const cells = ordered.map((id) => this.cache.get(id)).filter((c) => c !== void 0);
|
|
929
|
-
const ok = cells.every((c) => c.status === "success" || c.status === "pass");
|
|
930
|
-
return { result: { cells, ok }, events };
|
|
931
|
-
}
|
|
932
|
-
/** The set of cells awaiting (re-)execution. */
|
|
933
|
-
staleIds() {
|
|
934
|
-
return [...this.stale];
|
|
935
|
-
}
|
|
936
|
-
/** Serialize the current workbook and write it atomically. */
|
|
937
|
-
save(path) {
|
|
938
|
-
const wb = this.require();
|
|
939
|
-
const target = path ?? this.path;
|
|
940
|
-
if (!target) throw new Error("No path to save to");
|
|
941
|
-
writeFileAtomic(target, serializeWorkbook(wb));
|
|
942
|
-
this.path = target;
|
|
943
|
-
this.dirty = false;
|
|
944
|
-
return target;
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
// src/contract.ts
|
|
949
|
-
var SCHEMA_VERSION = { major: 1, minor: 0 };
|
|
950
|
-
var VERSION = "0.1.0";
|
|
951
|
-
var COMMAND_NAMES = [
|
|
952
|
-
"run",
|
|
953
|
-
"describe",
|
|
954
|
-
"validate",
|
|
955
|
-
"graph",
|
|
956
|
-
"strip",
|
|
957
|
-
"new",
|
|
958
|
-
"capabilities",
|
|
959
|
-
"templates",
|
|
960
|
-
"cell",
|
|
961
|
-
"serve",
|
|
962
|
-
"functions",
|
|
963
|
-
"meta",
|
|
964
|
-
"import",
|
|
965
|
-
"export"
|
|
966
|
-
];
|
|
967
|
-
|
|
968
|
-
// src/introspect.ts
|
|
969
|
-
import * as mathFunctions from "@danielsimonjr/mathts-functions";
|
|
970
|
-
var functionsCache;
|
|
971
|
-
function listFunctions() {
|
|
972
|
-
if (functionsCache) return functionsCache;
|
|
973
|
-
const functions = [];
|
|
974
|
-
const constants = [];
|
|
975
|
-
for (const [name, value] of Object.entries(mathFunctions)) {
|
|
976
|
-
if (typeof value === "function") functions.push(name);
|
|
977
|
-
else if (value !== null && typeof value !== "object") constants.push(name);
|
|
978
|
-
}
|
|
979
|
-
functionsCache = { functions: functions.sort(), constants: constants.sort() };
|
|
980
|
-
return functionsCache;
|
|
981
|
-
}
|
|
982
|
-
var CELL_SCHEMAS = {
|
|
983
|
-
code: "MathTS expression; the cell output is its evaluated value",
|
|
984
|
-
markdown: "Markdown text (rendered to HTML on export)",
|
|
985
|
-
equation: "MathTS expression syntax, rendered to MathML on export (display-only)",
|
|
986
|
-
test: "Boolean MathTS expression; true = pass, false = fail",
|
|
987
|
-
data: "A YAML/JSON literal value (e.g. a numeric array)",
|
|
988
|
-
visualization: "YAML chart spec: { type: line|scatter|bar, title?, x: {label?, data}, y: {label?, data} }; data = a dependency cell id (numeric array) or an inline array"
|
|
989
|
-
};
|
|
990
|
-
function capabilitiesInfo() {
|
|
991
|
-
return {
|
|
992
|
-
name: "mtsw",
|
|
993
|
-
version: VERSION,
|
|
994
|
-
schemaVersion: SCHEMA_VERSION,
|
|
995
|
-
cellTypes: {
|
|
996
|
-
supported: [...SUPPORTED_CELL_TYPES],
|
|
997
|
-
deferred: ["tensor", "export"]
|
|
998
|
-
},
|
|
999
|
-
cellSchemas: CELL_SCHEMAS,
|
|
1000
|
-
commands: [...COMMAND_NAMES],
|
|
1001
|
-
features: {
|
|
1002
|
-
json: true,
|
|
1003
|
-
write: true,
|
|
1004
|
-
runCell: true,
|
|
1005
|
-
editCell: true,
|
|
1006
|
-
serve: true,
|
|
1007
|
-
incremental: true,
|
|
1008
|
-
functions: true,
|
|
1009
|
-
import: true,
|
|
1010
|
-
export: true
|
|
1011
|
-
}
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
// src/doc.ts
|
|
1016
|
-
function describeData(wb) {
|
|
1017
|
-
const cells = (wb?.cells ?? []).map((c) => ({
|
|
1018
|
-
id: c.id,
|
|
1019
|
-
type: c.type,
|
|
1020
|
-
content: c.content,
|
|
1021
|
-
dependsOn: c.dependsOn ?? [],
|
|
1022
|
-
metadata: c.metadata ?? {},
|
|
1023
|
-
output: c.output ?? null,
|
|
1024
|
-
error: c.error ?? null
|
|
1025
|
-
}));
|
|
1026
|
-
const graph = wb ? buildDependencyGraph(wb.cells) : void 0;
|
|
1027
|
-
const edges = graph ? [...graph.nodes].flatMap(([id, node]) => node.dependencies.map((dep) => ({ from: dep, to: id }))) : [];
|
|
1028
|
-
const cycles = graph ? detectCycles(graph) : [];
|
|
1029
|
-
return {
|
|
1030
|
-
version: wb?.version ?? "1.0",
|
|
1031
|
-
metadata: wb?.metadata ?? {},
|
|
1032
|
-
runtime: wb?.runtime ?? { engine: "mathts", execution: "reactive" },
|
|
1033
|
-
cells,
|
|
1034
|
-
graph: { edges, cycles }
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
// src/rpc.ts
|
|
1039
|
-
var CODE = {
|
|
1040
|
-
PARSE: -32700,
|
|
1041
|
-
INVALID_REQUEST: -32600,
|
|
1042
|
-
METHOD_NOT_FOUND: -32601,
|
|
1043
|
-
INVALID_PARAMS: -32602,
|
|
1044
|
-
INTERNAL: -32603
|
|
1045
|
-
};
|
|
1046
|
-
function str(value) {
|
|
1047
|
-
return typeof value === "string" ? value : void 0;
|
|
1048
|
-
}
|
|
1049
|
-
async function handleRequest(session, request) {
|
|
1050
|
-
const id = request && request.id !== void 0 ? request.id : null;
|
|
1051
|
-
const ok = (result, events = [], shutdown = false) => ({
|
|
1052
|
-
response: { jsonrpc: "2.0", id, result },
|
|
1053
|
-
events,
|
|
1054
|
-
shutdown
|
|
1055
|
-
});
|
|
1056
|
-
const fail = (code, message) => ({
|
|
1057
|
-
response: { jsonrpc: "2.0", id, error: { code, message } },
|
|
1058
|
-
events: [],
|
|
1059
|
-
shutdown: false
|
|
1060
|
-
});
|
|
1061
|
-
if (!request || typeof request.method !== "string") {
|
|
1062
|
-
return fail(CODE.INVALID_REQUEST, "Invalid request: missing method");
|
|
1063
|
-
}
|
|
1064
|
-
const p = request.params ?? {};
|
|
1065
|
-
const doc = () => describeData(session.workbook ?? void 0);
|
|
1066
|
-
try {
|
|
1067
|
-
switch (request.method) {
|
|
1068
|
-
case "open": {
|
|
1069
|
-
const path = str(p.path);
|
|
1070
|
-
if (path === void 0) return fail(CODE.INVALID_PARAMS, "open requires params.path");
|
|
1071
|
-
session.open(path, { force: p.force === true });
|
|
1072
|
-
return ok({ path: session.path, ...doc() });
|
|
1073
|
-
}
|
|
1074
|
-
case "describe":
|
|
1075
|
-
return ok(doc());
|
|
1076
|
-
case "validate": {
|
|
1077
|
-
const d = doc();
|
|
1078
|
-
const problems = d.graph.cycles.map((c) => `Dependency cycle: ${c.join(" -> ")}`);
|
|
1079
|
-
return ok({ ok: problems.length === 0, problems, cellCount: d.cells.length });
|
|
1080
|
-
}
|
|
1081
|
-
case "graph":
|
|
1082
|
-
return ok(doc().graph);
|
|
1083
|
-
case "run": {
|
|
1084
|
-
const { result, events } = await session.run(str(p.only));
|
|
1085
|
-
const evts = events.map((e) => ({
|
|
1086
|
-
jsonrpc: "2.0",
|
|
1087
|
-
method: "cell/event",
|
|
1088
|
-
params: { type: e.type, cellId: e.cellId, error: e.error }
|
|
1089
|
-
}));
|
|
1090
|
-
return ok(result, evts);
|
|
1091
|
-
}
|
|
1092
|
-
case "cell/add":
|
|
1093
|
-
session.addCell(
|
|
1094
|
-
{
|
|
1095
|
-
id: str(p.id) ?? "",
|
|
1096
|
-
type: str(p.type) ?? "",
|
|
1097
|
-
content: str(p.content),
|
|
1098
|
-
dependsOn: Array.isArray(p.dependsOn) ? p.dependsOn : void 0
|
|
1099
|
-
},
|
|
1100
|
-
p.position
|
|
1101
|
-
);
|
|
1102
|
-
return ok(doc());
|
|
1103
|
-
case "cell/edit":
|
|
1104
|
-
session.editCell(str(p.id) ?? "", {
|
|
1105
|
-
content: str(p.content),
|
|
1106
|
-
type: str(p.type),
|
|
1107
|
-
dependsOn: Array.isArray(p.dependsOn) ? p.dependsOn : void 0
|
|
1108
|
-
});
|
|
1109
|
-
return ok(doc());
|
|
1110
|
-
case "cell/rm": {
|
|
1111
|
-
const changedCells = session.removeCell(str(p.id) ?? "", { force: p.force === true });
|
|
1112
|
-
return ok({ changedCells, ...doc() });
|
|
1113
|
-
}
|
|
1114
|
-
case "cell/move":
|
|
1115
|
-
session.moveCell(str(p.id) ?? "", p.position ?? {});
|
|
1116
|
-
return ok(doc());
|
|
1117
|
-
case "cell/rename":
|
|
1118
|
-
session.renameCell(str(p.oldId) ?? "", str(p.newId) ?? "");
|
|
1119
|
-
return ok(doc());
|
|
1120
|
-
case "meta/get":
|
|
1121
|
-
return ok(session.workbook?.metadata ?? {});
|
|
1122
|
-
case "meta/set":
|
|
1123
|
-
session.setMetadata({
|
|
1124
|
-
title: str(p.title),
|
|
1125
|
-
author: str(p.author),
|
|
1126
|
-
description: str(p.description),
|
|
1127
|
-
tags: Array.isArray(p.tags) ? p.tags : void 0
|
|
1128
|
-
});
|
|
1129
|
-
return ok(session.workbook?.metadata ?? {});
|
|
1130
|
-
case "save":
|
|
1131
|
-
return ok({ path: session.save(str(p.path)) });
|
|
1132
|
-
case "capabilities":
|
|
1133
|
-
return ok(capabilitiesInfo());
|
|
1134
|
-
case "functions":
|
|
1135
|
-
return ok(listFunctions());
|
|
1136
|
-
case "shutdown":
|
|
1137
|
-
return ok({ ok: true }, [], true);
|
|
1138
|
-
default:
|
|
1139
|
-
return fail(CODE.METHOD_NOT_FOUND, `Method not found: ${request.method}`);
|
|
1140
|
-
}
|
|
1141
|
-
} catch (error) {
|
|
1142
|
-
return fail(CODE.INTERNAL, error instanceof Error ? error.message : String(error));
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
639
|
// src/formatter.ts
|
|
1147
640
|
function bigintReplacer(_key, val) {
|
|
1148
641
|
return typeof val === "bigint" ? `${val}n` : val;
|
|
@@ -1206,7 +699,6 @@ function formatResult(value) {
|
|
|
1206
699
|
}
|
|
1207
700
|
|
|
1208
701
|
export {
|
|
1209
|
-
writeFileAtomic,
|
|
1210
702
|
parseYamlHardened,
|
|
1211
703
|
buildDependencyGraph,
|
|
1212
704
|
topologicalSort,
|
|
@@ -1215,6 +707,8 @@ export {
|
|
|
1215
707
|
toMermaid,
|
|
1216
708
|
toDOT,
|
|
1217
709
|
detectCycles,
|
|
710
|
+
isValidIdentifier,
|
|
711
|
+
SUPPORTED_CELL_TYPES,
|
|
1218
712
|
detectCellType,
|
|
1219
713
|
parseWorkbook,
|
|
1220
714
|
serializeWorkbook,
|
|
@@ -1222,18 +716,5 @@ export {
|
|
|
1222
716
|
importWorkbook,
|
|
1223
717
|
WorkbookExecutor,
|
|
1224
718
|
createExecutor,
|
|
1225
|
-
addCell,
|
|
1226
|
-
editCell,
|
|
1227
|
-
removeCell,
|
|
1228
|
-
moveCell,
|
|
1229
|
-
renameCell,
|
|
1230
|
-
setMetadata,
|
|
1231
|
-
Session,
|
|
1232
|
-
SCHEMA_VERSION,
|
|
1233
|
-
VERSION,
|
|
1234
|
-
listFunctions,
|
|
1235
|
-
capabilitiesInfo,
|
|
1236
|
-
describeData,
|
|
1237
|
-
handleRequest,
|
|
1238
719
|
formatResult
|
|
1239
720
|
};
|