@danielsimonjr/mathts-workbook 0.1.8 → 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.
@@ -0,0 +1,720 @@
1
+ // src/yaml-safe.ts
2
+ import { parse as parseYaml } from "yaml";
3
+ var POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
4
+ function parseYamlHardened(content) {
5
+ return parseYaml(content, { schema: "core", merge: false, logLevel: "silent" });
6
+ }
7
+ function isPlainObject(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ function findPollutionKeys(value, seen = /* @__PURE__ */ new WeakSet(), found = []) {
11
+ if (!isPlainObject(value) && !Array.isArray(value)) return found;
12
+ if (seen.has(value)) return found;
13
+ seen.add(value);
14
+ if (Array.isArray(value)) {
15
+ for (const item of value) findPollutionKeys(item, seen, found);
16
+ return found;
17
+ }
18
+ for (const key of Object.getOwnPropertyNames(value)) {
19
+ if (POLLUTION_KEYS.has(key)) {
20
+ found.push(key);
21
+ continue;
22
+ }
23
+ findPollutionKeys(value[key], seen, found);
24
+ }
25
+ return found;
26
+ }
27
+ function assertNoPollution(value) {
28
+ const keys = findPollutionKeys(value);
29
+ if (keys.length > 0) {
30
+ throw new Error(`Disallowed key "${keys[0]}" (prototype pollution)`);
31
+ }
32
+ }
33
+
34
+ // src/graph.ts
35
+ function buildDependencyGraph(cells) {
36
+ const nodes = /* @__PURE__ */ new Map();
37
+ for (const cell of cells) {
38
+ nodes.set(cell.id, {
39
+ id: cell.id,
40
+ dependencies: cell.dependsOn ?? [],
41
+ dependents: []
42
+ });
43
+ }
44
+ for (const cell of cells) {
45
+ for (const depId of cell.dependsOn ?? []) {
46
+ const depNode = nodes.get(depId);
47
+ if (depNode) {
48
+ depNode.dependents.push(cell.id);
49
+ }
50
+ }
51
+ }
52
+ const executionOrder = topologicalSort(nodes);
53
+ return { nodes, executionOrder };
54
+ }
55
+ function topologicalSort(nodes) {
56
+ const visited = /* @__PURE__ */ new Set();
57
+ const result = [];
58
+ function visit(id) {
59
+ if (visited.has(id)) return;
60
+ visited.add(id);
61
+ const node = nodes.get(id);
62
+ if (node) {
63
+ for (const depId of node.dependencies) {
64
+ visit(depId);
65
+ }
66
+ }
67
+ result.push(id);
68
+ }
69
+ for (const id of nodes.keys()) {
70
+ visit(id);
71
+ }
72
+ return result;
73
+ }
74
+ function getDependents(graph, cellId) {
75
+ const result = [];
76
+ const visited = /* @__PURE__ */ new Set();
77
+ function collect(id) {
78
+ const node = graph.nodes.get(id);
79
+ if (!node) return;
80
+ for (const depId of node.dependents) {
81
+ if (!visited.has(depId)) {
82
+ visited.add(depId);
83
+ result.push(depId);
84
+ collect(depId);
85
+ }
86
+ }
87
+ }
88
+ collect(cellId);
89
+ return result;
90
+ }
91
+ function getAncestors(graph, id) {
92
+ const result = /* @__PURE__ */ new Set();
93
+ function visit(nodeId) {
94
+ if (result.has(nodeId)) return;
95
+ const node = graph.nodes.get(nodeId);
96
+ if (!node) return;
97
+ result.add(nodeId);
98
+ for (const dep of node.dependencies) visit(dep);
99
+ }
100
+ visit(id);
101
+ return graph.executionOrder.filter((nodeId) => result.has(nodeId));
102
+ }
103
+ function toMermaid(graph) {
104
+ const lines = ["graph TD"];
105
+ for (const id of graph.nodes.keys()) {
106
+ lines.push(` ${id}["${id}"]`);
107
+ }
108
+ for (const [id, node] of graph.nodes) {
109
+ for (const dep of node.dependencies) {
110
+ lines.push(` ${dep} --> ${id}`);
111
+ }
112
+ }
113
+ return lines.join("\n");
114
+ }
115
+ function toDOT(graph) {
116
+ const lines = ["digraph deps {"];
117
+ for (const id of graph.nodes.keys()) {
118
+ lines.push(` ${id} [label="${id}"];`);
119
+ }
120
+ for (const [id, node] of graph.nodes) {
121
+ for (const dep of node.dependencies) {
122
+ lines.push(` ${dep} -> ${id};`);
123
+ }
124
+ }
125
+ lines.push("}");
126
+ return lines.join("\n");
127
+ }
128
+ function detectCycles(graph) {
129
+ const cycles = [];
130
+ const visited = /* @__PURE__ */ new Set();
131
+ const stack = /* @__PURE__ */ new Set();
132
+ function dfs(id, path) {
133
+ if (stack.has(id)) {
134
+ const cycleStart = path.indexOf(id);
135
+ cycles.push(path.slice(cycleStart));
136
+ return true;
137
+ }
138
+ if (visited.has(id)) return false;
139
+ visited.add(id);
140
+ stack.add(id);
141
+ const node = graph.nodes.get(id);
142
+ if (node) {
143
+ for (const depId of node.dependencies) {
144
+ dfs(depId, [...path, id]);
145
+ }
146
+ }
147
+ stack.delete(id);
148
+ return false;
149
+ }
150
+ for (const id of graph.nodes.keys()) {
151
+ if (!visited.has(id)) {
152
+ dfs(id, []);
153
+ }
154
+ }
155
+ return cycles;
156
+ }
157
+
158
+ // src/parser.ts
159
+ import { stringify as stringifyYaml } from "yaml";
160
+ var CELL_TYPE_KEYS = [
161
+ "markdown",
162
+ "code",
163
+ "tensor",
164
+ "equation",
165
+ "visualization",
166
+ "data",
167
+ "test",
168
+ "export"
169
+ ];
170
+ var RESERVED_CELL_KEYS = ["id", "depends_on", "language", "format", "output", "error"];
171
+ var IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
172
+ function isValidIdentifier(id) {
173
+ return typeof id === "string" && IDENTIFIER_RE.test(id);
174
+ }
175
+ var SUPPORTED_CELL_TYPES = ["code", "markdown", "data", "test", "equation", "visualization"];
176
+ var EXECUTION_MODES = ["reactive", "sequential", "manual"];
177
+ function detectCellType(cell) {
178
+ for (const key of CELL_TYPE_KEYS) {
179
+ if (key in cell) {
180
+ return key;
181
+ }
182
+ }
183
+ return "code";
184
+ }
185
+ function isPlainObject2(value) {
186
+ return typeof value === "object" && value !== null && !Array.isArray(value);
187
+ }
188
+ function toContent(raw) {
189
+ if (raw === null || raw === void 0) return "";
190
+ if (typeof raw === "string") return raw;
191
+ return stringifyYaml(raw).trim();
192
+ }
193
+ function normalizeRuntime(raw) {
194
+ const rt = isPlainObject2(raw) ? raw : {};
195
+ const runtime = {
196
+ engine: rt.engine === "custom" ? "custom" : "mathts",
197
+ execution: typeof rt.execution === "string" && EXECUTION_MODES.includes(rt.execution) ? rt.execution : "reactive"
198
+ };
199
+ if (typeof rt.timeout === "number") runtime.timeout = rt.timeout;
200
+ return runtime;
201
+ }
202
+ function mapCell(raw, index, errors) {
203
+ if (!isPlainObject2(raw)) {
204
+ errors.push(`Cell at index ${index}: must be a mapping`);
205
+ return { id: `#${index}`, type: "code", content: "" };
206
+ }
207
+ const rawId = raw.id;
208
+ let id;
209
+ if (rawId === void 0 || rawId === null) {
210
+ errors.push(`Cell at index ${index}: missing "id"`);
211
+ id = `#${index}`;
212
+ } 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
+ );
216
+ id = String(rawId);
217
+ } else {
218
+ id = rawId;
219
+ }
220
+ const presentTypeKeys = CELL_TYPE_KEYS.filter(
221
+ (k) => Object.prototype.hasOwnProperty.call(raw, k)
222
+ );
223
+ if (presentTypeKeys.length === 0) {
224
+ errors.push(
225
+ `Cell "${id}": no recognized type key (expected one of ${CELL_TYPE_KEYS.join(", ")})`
226
+ );
227
+ } else if (presentTypeKeys.length > 1) {
228
+ errors.push(`Cell "${id}": multiple type keys (${presentTypeKeys.join(", ")})`);
229
+ }
230
+ const type = presentTypeKeys[0] ?? "code";
231
+ const content = presentTypeKeys[0] ? toContent(raw[presentTypeKeys[0]]) : "";
232
+ if (presentTypeKeys[0] && !SUPPORTED_CELL_TYPES.includes(type)) {
233
+ errors.push(
234
+ `Cell "${id}": type '${type}' is reserved but not yet supported (supported: ${SUPPORTED_CELL_TYPES.join(", ")})`
235
+ );
236
+ }
237
+ let dependsOn;
238
+ if (raw.depends_on !== void 0) {
239
+ if (!Array.isArray(raw.depends_on)) {
240
+ errors.push(`Cell "${id}": depends_on must be a list`);
241
+ } else {
242
+ dependsOn = raw.depends_on.map((d) => String(d));
243
+ }
244
+ }
245
+ const metadata = {};
246
+ for (const key of Object.keys(raw)) {
247
+ if (RESERVED_CELL_KEYS.includes(key) || CELL_TYPE_KEYS.includes(key)) continue;
248
+ metadata[key] = raw[key];
249
+ }
250
+ const cell = { id, type, content };
251
+ if (dependsOn) cell.dependsOn = dependsOn;
252
+ if (Object.keys(metadata).length > 0) cell.metadata = metadata;
253
+ if (raw.output !== void 0) cell.output = raw.output;
254
+ if (raw.error !== void 0) cell.error = String(raw.error);
255
+ return cell;
256
+ }
257
+ function parseWorkbook(content) {
258
+ try {
259
+ if (!content.trim()) {
260
+ return { success: false, errors: ["Empty workbook content"] };
261
+ }
262
+ const errors = [];
263
+ const warnings = [];
264
+ const doc = parseYamlHardened(content);
265
+ if (!isPlainObject2(doc)) {
266
+ return { success: false, errors: ["Workbook must be a YAML mapping"] };
267
+ }
268
+ for (const key of findPollutionKeys(doc)) {
269
+ errors.push(`Disallowed key "${key}" (prototype pollution)`);
270
+ }
271
+ const metadata = isPlainObject2(doc.metadata) ? doc.metadata : {};
272
+ const workbook = {
273
+ version: doc.version != null ? String(doc.version) : "1.0",
274
+ metadata,
275
+ runtime: normalizeRuntime(doc.runtime),
276
+ cells: []
277
+ };
278
+ if (doc.cells === void 0) {
279
+ warnings.push("Workbook has no cells");
280
+ } else if (!Array.isArray(doc.cells)) {
281
+ errors.push('Workbook "cells" must be a list');
282
+ } else {
283
+ const idSet = /* @__PURE__ */ new Set();
284
+ const cells = doc.cells.map((raw, i) => {
285
+ const cell = mapCell(raw, i, errors);
286
+ if (IDENTIFIER_RE.test(cell.id)) {
287
+ if (idSet.has(cell.id)) {
288
+ errors.push(`Duplicate cell id: "${cell.id}"`);
289
+ } else {
290
+ idSet.add(cell.id);
291
+ }
292
+ }
293
+ return cell;
294
+ });
295
+ for (const cell of cells) {
296
+ for (const dep of cell.dependsOn ?? []) {
297
+ if (!idSet.has(dep)) {
298
+ errors.push(`Cell "${cell.id}": depends_on references unknown cell "${dep}"`);
299
+ }
300
+ }
301
+ }
302
+ workbook.cells = cells;
303
+ }
304
+ const success = errors.length === 0;
305
+ return {
306
+ success,
307
+ workbook: success ? workbook : void 0,
308
+ errors: errors.length > 0 ? errors : void 0,
309
+ warnings: warnings.length > 0 ? warnings : void 0
310
+ };
311
+ } catch (error) {
312
+ return {
313
+ success: false,
314
+ errors: [`Parse error: ${error instanceof Error ? error.message : String(error)}`]
315
+ };
316
+ }
317
+ }
318
+ function serializeCell(cell) {
319
+ const out = { [cell.type]: cell.content, id: cell.id };
320
+ if (cell.dependsOn && cell.dependsOn.length > 0) out.depends_on = cell.dependsOn;
321
+ if (cell.metadata) {
322
+ for (const [key, value] of Object.entries(cell.metadata)) {
323
+ if (CELL_TYPE_KEYS.includes(key) || RESERVED_CELL_KEYS.includes(key)) continue;
324
+ out[key] = value;
325
+ }
326
+ }
327
+ if (cell.output !== void 0) out.output = cell.output;
328
+ if (cell.error !== void 0) out.error = String(cell.error);
329
+ return out;
330
+ }
331
+ function serializeWorkbook(workbook) {
332
+ if (!workbook || !Array.isArray(workbook.cells)) {
333
+ throw new Error('serializeWorkbook: invalid workbook (missing "cells" array)');
334
+ }
335
+ const runtime = {
336
+ engine: workbook.runtime?.engine ?? "mathts",
337
+ execution: workbook.runtime?.execution ?? "reactive"
338
+ };
339
+ if (workbook.runtime?.timeout !== void 0) runtime.timeout = workbook.runtime.timeout;
340
+ const doc = {
341
+ version: String(workbook.version ?? "1.0"),
342
+ metadata: workbook.metadata ?? {},
343
+ runtime,
344
+ cells: workbook.cells.map(serializeCell)
345
+ };
346
+ return stringifyYaml(doc, { lineWidth: 0 });
347
+ }
348
+ function stripOutputs(workbook) {
349
+ return {
350
+ ...workbook,
351
+ cells: workbook.cells.map((cell) => ({
352
+ ...cell,
353
+ output: void 0,
354
+ error: void 0
355
+ }))
356
+ };
357
+ }
358
+ function importWorkbook(input) {
359
+ let obj;
360
+ try {
361
+ obj = parseYamlHardened(input);
362
+ } catch (error) {
363
+ return { success: false, errors: [`Invalid JSON/YAML: ${error instanceof Error ? error.message : String(error)}`] };
364
+ }
365
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
366
+ return { success: false, errors: ['Input must be an object with a "cells" array'] };
367
+ }
368
+ const root = obj;
369
+ if (!Array.isArray(root.cells)) {
370
+ return { success: false, errors: ['Input must have a "cells" array'] };
371
+ }
372
+ const errors = [];
373
+ const cells = [];
374
+ root.cells.forEach((raw, index) => {
375
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
376
+ errors.push(`Cell at index ${index}: must be an object`);
377
+ return;
378
+ }
379
+ const c = raw;
380
+ const id = typeof c.id === "string" ? c.id : "";
381
+ const type = typeof c.type === "string" ? c.type : "";
382
+ const content = typeof c.content === "string" ? c.content : c.content == null ? "" : String(c.content);
383
+ const depsRaw = Array.isArray(c.dependsOn) ? c.dependsOn : Array.isArray(c.depends_on) ? c.depends_on : void 0;
384
+ const dependsOn = depsRaw?.map((d) => String(d));
385
+ const cell = { id, type, content };
386
+ if (dependsOn && dependsOn.length > 0) cell.dependsOn = dependsOn;
387
+ cells.push(cell);
388
+ });
389
+ if (errors.length > 0) return { success: false, errors };
390
+ const metadata = root.metadata && typeof root.metadata === "object" && !Array.isArray(root.metadata) ? root.metadata : {};
391
+ const runtime = root.runtime && typeof root.runtime === "object" && !Array.isArray(root.runtime) ? root.runtime : { engine: "mathts", execution: "reactive" };
392
+ const version = typeof root.version === "string" ? root.version : "1.0";
393
+ const workbook = { version, metadata, runtime, cells };
394
+ const parsed = parseWorkbook(serializeWorkbook(workbook));
395
+ if (!parsed.success || !parsed.workbook) return parsed;
396
+ const cycles = detectCycles(buildDependencyGraph(parsed.workbook.cells));
397
+ if (cycles.length > 0) {
398
+ return { success: false, errors: cycles.map((c) => `Dependency cycle: ${c.join(" -> ")}`) };
399
+ }
400
+ return parsed;
401
+ }
402
+
403
+ // src/executor.ts
404
+ import { evaluate } from "@danielsimonjr/mathts-functions";
405
+ var WorkbookExecutor = class {
406
+ workbook;
407
+ graph;
408
+ outputs = /* @__PURE__ */ new Map();
409
+ handlers = [];
410
+ constructor(workbook) {
411
+ this.workbook = workbook;
412
+ this.graph = buildDependencyGraph(workbook.cells);
413
+ }
414
+ /**
415
+ * Subscribe to execution events
416
+ */
417
+ on(handler) {
418
+ this.handlers.push(handler);
419
+ return () => {
420
+ const index = this.handlers.indexOf(handler);
421
+ if (index >= 0) {
422
+ this.handlers.splice(index, 1);
423
+ }
424
+ };
425
+ }
426
+ /**
427
+ * Emit an event to all handlers
428
+ */
429
+ emit(event) {
430
+ for (const handler of this.handlers) {
431
+ handler(event);
432
+ }
433
+ }
434
+ /**
435
+ * Run all cells in order
436
+ */
437
+ async runAll() {
438
+ for (const cellId of this.graph.executionOrder) {
439
+ await this.runCell(cellId);
440
+ }
441
+ this.emit({
442
+ type: "workbook:complete",
443
+ timestamp: Date.now()
444
+ });
445
+ }
446
+ /**
447
+ * Run a specific cell
448
+ */
449
+ async runCell(cellId) {
450
+ const cell = this.workbook.cells.find((c) => c.id === cellId);
451
+ if (!cell) {
452
+ throw new Error(`Cell not found: ${cellId}`);
453
+ }
454
+ this.emit({
455
+ type: "cell:start",
456
+ cellId,
457
+ timestamp: Date.now()
458
+ });
459
+ try {
460
+ const output = await this.executeCell(cell);
461
+ this.outputs.set(cellId, output);
462
+ this.emit({
463
+ type: "cell:success",
464
+ cellId,
465
+ output,
466
+ timestamp: Date.now()
467
+ });
468
+ if (this.workbook.runtime.execution === "reactive") {
469
+ const dependents = getDependents(this.graph, cellId);
470
+ for (const depId of dependents) {
471
+ this.emit({
472
+ type: "cell:stale",
473
+ cellId: depId,
474
+ timestamp: Date.now()
475
+ });
476
+ }
477
+ }
478
+ return output;
479
+ } catch (error) {
480
+ const errorMessage = error instanceof Error ? error.message : String(error);
481
+ this.emit({
482
+ type: "cell:error",
483
+ cellId,
484
+ error: errorMessage,
485
+ timestamp: Date.now()
486
+ });
487
+ throw error;
488
+ }
489
+ }
490
+ /**
491
+ * Execute a single cell
492
+ */
493
+ async executeCell(cell) {
494
+ switch (cell.type) {
495
+ case "code":
496
+ return this.executeCode(cell);
497
+ case "test":
498
+ return this.executeTest(cell);
499
+ case "markdown":
500
+ case "equation":
501
+ case "visualization":
502
+ return cell.content;
503
+ // Display-only cells: pass content through, no eval
504
+ case "data":
505
+ return this.executeData(cell);
506
+ default:
507
+ throw new Error(`Unsupported cell type: ${cell.type}`);
508
+ }
509
+ }
510
+ /**
511
+ * Build the evaluation scope from a cell's DIRECT dependency outputs.
512
+ *
513
+ * Dependency outputs are injected as named variables keyed by the
514
+ * dependency's cell id, so a cell can reference an earlier cell's result by
515
+ * that id. Injection is non-transitive: only ids in `cell.dependsOn` are
516
+ * injected (a dependency's own dependencies are not). An output of
517
+ * `undefined` (cell never ran) is skipped, so the reference fails loudly.
518
+ */
519
+ buildScope(cell) {
520
+ const scope = {};
521
+ if (cell.dependsOn) {
522
+ for (const depId of cell.dependsOn) {
523
+ if (this.outputs.has(depId)) {
524
+ scope[depId] = this.outputs.get(depId);
525
+ }
526
+ }
527
+ }
528
+ return scope;
529
+ }
530
+ /**
531
+ * Execute a code cell by evaluating its content as a MathTS expression.
532
+ *
533
+ * Evaluation goes through the MathTS expression engine — property and
534
+ * method access route through the expression sandbox, so this does not
535
+ * have the arbitrary-code-execution exposure of the `Function` constructor.
536
+ */
537
+ async executeCode(cell) {
538
+ return evaluate(cell.content, this.buildScope(cell));
539
+ }
540
+ /**
541
+ * Execute a test cell: evaluate its assertion expression in the dependency
542
+ * scope. The result must be a boolean — `runReport` classifies `true` as a
543
+ * pass and `false` as a fail. A non-boolean result is a usage error (a test
544
+ * that doesn't assert), surfaced via throw like any other evaluation error.
545
+ */
546
+ async executeTest(cell) {
547
+ const result = evaluate(cell.content, this.buildScope(cell));
548
+ if (typeof result !== "boolean") {
549
+ throw new Error(`test cell must evaluate to a boolean, got ${typeof result}`);
550
+ }
551
+ return result;
552
+ }
553
+ /**
554
+ * Execute a data cell — parse its content as YAML (a superset of JSON) using
555
+ * the same hardened options + prototype-pollution guard as the document
556
+ * parser, so both YAML entry points are consistent.
557
+ */
558
+ async executeData(cell) {
559
+ const value = parseYamlHardened(cell.content);
560
+ assertNoPollution(value);
561
+ return value;
562
+ }
563
+ /**
564
+ * Run every cell in dependency order and return a structured report.
565
+ *
566
+ * Unlike {@link runAll} (the event-stream API, which throws on the first cell
567
+ * error), `runReport` is the headless/report API: it is **continue-on-error**
568
+ * (one failing cell does not abort the rest) and never throws on a cell
569
+ * failure. A dependency cycle is refused up front. Test cells are classified
570
+ * as `pass`/`fail`/`error`; all other cells as `success`/`error`.
571
+ */
572
+ /**
573
+ * Seed the output cache (for incremental session runs): non-stale cells'
574
+ * outputs are provided so downstream cells can read them as scope without
575
+ * re-execution. Replaces any existing cached outputs.
576
+ */
577
+ seedOutputs(entries) {
578
+ this.outputs = new Map(entries);
579
+ }
580
+ async runReport(options = {}) {
581
+ const cells = [];
582
+ const cycles = detectCycles(this.graph);
583
+ if (cycles.length > 0) {
584
+ const description = cycles.map((cycle) => cycle.join(" -> ")).join("; ");
585
+ cells.push({
586
+ id: "(workbook)",
587
+ type: "code",
588
+ status: "error",
589
+ error: `Dependency cycle detected: ${description}`
590
+ });
591
+ return { cells, ok: false };
592
+ }
593
+ let order = this.graph.executionOrder;
594
+ if (options.only !== void 0) {
595
+ const allowed = new Set(getAncestors(this.graph, options.only));
596
+ order = order.filter((id) => allowed.has(id));
597
+ }
598
+ for (const cellId of order) {
599
+ if (options.runIds && !options.runIds.has(cellId)) continue;
600
+ const cell = this.workbook.cells.find((c) => c.id === cellId);
601
+ if (!cell) continue;
602
+ try {
603
+ const output = await this.runCell(cellId);
604
+ if (cell.type === "test") {
605
+ const passed = output === true;
606
+ cells.push({
607
+ id: cell.id,
608
+ type: cell.type,
609
+ status: passed ? "pass" : "fail",
610
+ output,
611
+ error: passed ? void 0 : `Assertion failed in cell '${cell.id}'`
612
+ });
613
+ } else {
614
+ cells.push({ id: cell.id, type: cell.type, status: "success", output });
615
+ }
616
+ } catch (error) {
617
+ cells.push({
618
+ id: cell.id,
619
+ type: cell.type,
620
+ status: "error",
621
+ error: error instanceof Error ? error.message : String(error)
622
+ });
623
+ }
624
+ }
625
+ const ok = cells.every((c) => c.status === "success" || c.status === "pass");
626
+ return { cells, ok };
627
+ }
628
+ /**
629
+ * Get output from a previous cell
630
+ */
631
+ getOutput(cellId) {
632
+ return this.outputs.get(cellId);
633
+ }
634
+ };
635
+ function createExecutor(workbook) {
636
+ return new WorkbookExecutor(workbook);
637
+ }
638
+
639
+ // src/formatter.ts
640
+ function bigintReplacer(_key, val) {
641
+ return typeof val === "bigint" ? `${val}n` : val;
642
+ }
643
+ function safeStringify(value) {
644
+ try {
645
+ const json = JSON.stringify(value, bigintReplacer);
646
+ if (json !== void 0) return json;
647
+ } catch {
648
+ }
649
+ try {
650
+ const seen = /* @__PURE__ */ new WeakSet();
651
+ const json = JSON.stringify(value, (_key, val) => {
652
+ if (typeof val === "bigint") return `${val}n`;
653
+ if (val !== null && typeof val === "object") {
654
+ if (seen.has(val)) return "[Circular]";
655
+ seen.add(val);
656
+ }
657
+ return val;
658
+ });
659
+ return json ?? "(no result)";
660
+ } catch {
661
+ try {
662
+ return String(value);
663
+ } catch {
664
+ return "[unserializable]";
665
+ }
666
+ }
667
+ }
668
+ function hasCustomToString(value) {
669
+ const toString = value.toString;
670
+ return typeof toString === "function" && toString !== Object.prototype.toString && toString !== Array.prototype.toString;
671
+ }
672
+ function formatResult(value) {
673
+ if (value === null || value === void 0) return "(no result)";
674
+ switch (typeof value) {
675
+ case "string":
676
+ return value;
677
+ case "number":
678
+ case "boolean":
679
+ return String(value);
680
+ case "bigint":
681
+ return `${value}n`;
682
+ case "function":
683
+ return "(function)";
684
+ case "symbol":
685
+ return value.toString();
686
+ case "object": {
687
+ if (!Array.isArray(value) && hasCustomToString(value)) {
688
+ try {
689
+ const text = value.toString();
690
+ if (text && text !== "[object Object]") return text;
691
+ } catch {
692
+ }
693
+ }
694
+ return safeStringify(value);
695
+ }
696
+ default:
697
+ return safeStringify(value);
698
+ }
699
+ }
700
+
701
+ export {
702
+ parseYamlHardened,
703
+ buildDependencyGraph,
704
+ topologicalSort,
705
+ getDependents,
706
+ getAncestors,
707
+ toMermaid,
708
+ toDOT,
709
+ detectCycles,
710
+ isValidIdentifier,
711
+ SUPPORTED_CELL_TYPES,
712
+ detectCellType,
713
+ parseWorkbook,
714
+ serializeWorkbook,
715
+ stripOutputs,
716
+ importWorkbook,
717
+ WorkbookExecutor,
718
+ createExecutor,
719
+ formatResult
720
+ };