@danielsimonjr/mathts-workbook 0.4.10 → 0.4.11

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,609 @@
1
+ import { parse, stringify } from "yaml";
2
+ import { evaluate } from "@danielsimonjr/mathts-functions";
3
+ //#region src/yaml-safe.ts
4
+ const POLLUTION_KEYS = /* @__PURE__ */ new Set([
5
+ "__proto__",
6
+ "constructor",
7
+ "prototype"
8
+ ]);
9
+ function parseYamlHardened(content) {
10
+ return parse(content, {
11
+ schema: "core",
12
+ merge: false,
13
+ logLevel: "silent"
14
+ });
15
+ }
16
+ function parseYamlSafe(content) {
17
+ const value = parseYamlHardened(content);
18
+ assertNoPollution(value);
19
+ return value;
20
+ }
21
+ function isPlainObject$1(value) {
22
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23
+ }
24
+ function findPollutionKeys(value, seen = /* @__PURE__ */ new WeakSet(), found = []) {
25
+ if (!isPlainObject$1(value) && !Array.isArray(value)) return found;
26
+ if (seen.has(value)) return found;
27
+ seen.add(value);
28
+ if (Array.isArray(value)) {
29
+ for (const item of value) findPollutionKeys(item, seen, found);
30
+ return found;
31
+ }
32
+ const proto = Object.getPrototypeOf(value);
33
+ if (proto !== Object.prototype && proto !== null) found.push("__proto__");
34
+ for (const key of Object.getOwnPropertyNames(value)) {
35
+ if (POLLUTION_KEYS.has(key)) {
36
+ found.push(key);
37
+ continue;
38
+ }
39
+ findPollutionKeys(value[key], seen, found);
40
+ }
41
+ return found;
42
+ }
43
+ function assertNoPollution(value) {
44
+ const keys = findPollutionKeys(value);
45
+ if (keys.length > 0) throw new Error(`Disallowed key "${keys[0]}" (prototype pollution)`);
46
+ }
47
+ //#endregion
48
+ //#region src/graph.ts
49
+ function buildDependencyGraph(cells) {
50
+ const nodes = /* @__PURE__ */ new Map();
51
+ for (const cell of cells) nodes.set(cell.id, {
52
+ id: cell.id,
53
+ dependencies: cell.dependsOn ?? [],
54
+ dependents: []
55
+ });
56
+ for (const cell of cells) for (const depId of cell.dependsOn ?? []) {
57
+ const depNode = nodes.get(depId);
58
+ if (depNode) depNode.dependents.push(cell.id);
59
+ }
60
+ return {
61
+ nodes,
62
+ executionOrder: topologicalSort(nodes)
63
+ };
64
+ }
65
+ function topologicalSort(nodes) {
66
+ const visited = /* @__PURE__ */ new Set();
67
+ const result = [];
68
+ function visit(id) {
69
+ if (visited.has(id)) return;
70
+ visited.add(id);
71
+ const node = nodes.get(id);
72
+ if (node) for (const depId of node.dependencies) visit(depId);
73
+ result.push(id);
74
+ }
75
+ for (const id of nodes.keys()) visit(id);
76
+ return result;
77
+ }
78
+ function getDependents(graph, cellId) {
79
+ const result = [];
80
+ const visited = /* @__PURE__ */ new Set();
81
+ function collect(id) {
82
+ const node = graph.nodes.get(id);
83
+ if (!node) return;
84
+ for (const depId of node.dependents) if (!visited.has(depId)) {
85
+ visited.add(depId);
86
+ result.push(depId);
87
+ collect(depId);
88
+ }
89
+ }
90
+ collect(cellId);
91
+ return result;
92
+ }
93
+ function getAncestors(graph, id) {
94
+ const result = /* @__PURE__ */ new Set();
95
+ function visit(nodeId) {
96
+ if (result.has(nodeId)) return;
97
+ const node = graph.nodes.get(nodeId);
98
+ if (!node) return;
99
+ result.add(nodeId);
100
+ for (const dep of node.dependencies) visit(dep);
101
+ }
102
+ visit(id);
103
+ return graph.executionOrder.filter((nodeId) => result.has(nodeId));
104
+ }
105
+ function toMermaid(graph) {
106
+ const lines = ["graph TD"];
107
+ for (const id of graph.nodes.keys()) lines.push(` ${id}["${id}"]`);
108
+ for (const [id, node] of graph.nodes) for (const dep of node.dependencies) lines.push(` ${dep} --> ${id}`);
109
+ return lines.join("\n");
110
+ }
111
+ function toDOT(graph) {
112
+ const lines = ["digraph deps {"];
113
+ for (const id of graph.nodes.keys()) lines.push(` ${id} [label="${id}"];`);
114
+ for (const [id, node] of graph.nodes) for (const dep of node.dependencies) lines.push(` ${dep} -> ${id};`);
115
+ lines.push("}");
116
+ return lines.join("\n");
117
+ }
118
+ function detectCycles(graph) {
119
+ const cycles = [];
120
+ const visited = /* @__PURE__ */ new Set();
121
+ const stack = /* @__PURE__ */ new Set();
122
+ function dfs(id, path) {
123
+ if (stack.has(id)) {
124
+ const cycleStart = path.indexOf(id);
125
+ cycles.push(path.slice(cycleStart));
126
+ return true;
127
+ }
128
+ if (visited.has(id)) return false;
129
+ visited.add(id);
130
+ stack.add(id);
131
+ const node = graph.nodes.get(id);
132
+ if (node) for (const depId of node.dependencies) dfs(depId, [...path, id]);
133
+ stack.delete(id);
134
+ return false;
135
+ }
136
+ for (const id of graph.nodes.keys()) if (!visited.has(id)) dfs(id, []);
137
+ return cycles;
138
+ }
139
+ //#endregion
140
+ //#region src/parser.ts
141
+ const CELL_TYPE_KEYS = [
142
+ "markdown",
143
+ "code",
144
+ "tensor",
145
+ "equation",
146
+ "visualization",
147
+ "data",
148
+ "test",
149
+ "export"
150
+ ];
151
+ const RESERVED_CELL_KEYS = [
152
+ "id",
153
+ "depends_on",
154
+ "language",
155
+ "format",
156
+ "output",
157
+ "error"
158
+ ];
159
+ const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
160
+ function isValidIdentifier(id) {
161
+ return typeof id === "string" && IDENTIFIER_RE.test(id);
162
+ }
163
+ const SUPPORTED_CELL_TYPES = [
164
+ "code",
165
+ "markdown",
166
+ "data",
167
+ "test",
168
+ "equation",
169
+ "visualization"
170
+ ];
171
+ const EXECUTION_MODES = [
172
+ "reactive",
173
+ "sequential",
174
+ "manual"
175
+ ];
176
+ function detectCellType(cell) {
177
+ for (const key of CELL_TYPE_KEYS) if (key in cell) return key;
178
+ return "code";
179
+ }
180
+ function isPlainObject(value) {
181
+ return typeof value === "object" && value !== null && !Array.isArray(value);
182
+ }
183
+ function toContent(raw) {
184
+ if (raw === null || raw === void 0) return "";
185
+ if (typeof raw === "string") return raw;
186
+ return stringify(raw).trim();
187
+ }
188
+ function normalizeRuntime(raw) {
189
+ const rt = isPlainObject(raw) ? raw : {};
190
+ const runtime = {
191
+ engine: rt.engine === "custom" ? "custom" : "mathts",
192
+ execution: typeof rt.execution === "string" && EXECUTION_MODES.includes(rt.execution) ? rt.execution : "reactive"
193
+ };
194
+ if (typeof rt.timeout === "number") runtime.timeout = rt.timeout;
195
+ return runtime;
196
+ }
197
+ function mapCell(raw, index, errors) {
198
+ if (!isPlainObject(raw)) {
199
+ errors.push(`Cell at index ${index}: must be a mapping`);
200
+ return {
201
+ id: `#${index}`,
202
+ type: "code",
203
+ content: ""
204
+ };
205
+ }
206
+ const rawId = raw.id;
207
+ let id;
208
+ if (rawId === void 0 || rawId === null) {
209
+ errors.push(`Cell at index ${index}: missing "id"`);
210
+ id = `#${index}`;
211
+ } else if (typeof rawId !== "string" || !IDENTIFIER_RE.test(rawId)) {
212
+ errors.push(`Cell "${String(rawId)}": id must be a valid identifier ([A-Za-z_][A-Za-z0-9_]*)`);
213
+ id = String(rawId);
214
+ } else id = rawId;
215
+ const presentTypeKeys = CELL_TYPE_KEYS.filter((k) => Object.prototype.hasOwnProperty.call(raw, k));
216
+ if (presentTypeKeys.length === 0) errors.push(`Cell "${id}": no recognized type key (expected one of ${CELL_TYPE_KEYS.join(", ")})`);
217
+ else if (presentTypeKeys.length > 1) errors.push(`Cell "${id}": multiple type keys (${presentTypeKeys.join(", ")})`);
218
+ const type = presentTypeKeys[0] ?? "code";
219
+ const content = presentTypeKeys[0] ? toContent(raw[presentTypeKeys[0]]) : "";
220
+ if (presentTypeKeys[0] && !SUPPORTED_CELL_TYPES.includes(type)) errors.push(`Cell "${id}": type '${type}' is reserved but not yet supported (supported: ${SUPPORTED_CELL_TYPES.join(", ")})`);
221
+ let dependsOn;
222
+ if (raw.depends_on !== void 0) {
223
+ if (!Array.isArray(raw.depends_on)) errors.push(`Cell "${id}": depends_on must be a list`);
224
+ else dependsOn = raw.depends_on.map((d) => String(d));
225
+ }
226
+ const metadata = {};
227
+ for (const key of Object.keys(raw)) {
228
+ if (RESERVED_CELL_KEYS.includes(key) || CELL_TYPE_KEYS.includes(key)) continue;
229
+ metadata[key] = raw[key];
230
+ }
231
+ const cell = {
232
+ id,
233
+ type,
234
+ content
235
+ };
236
+ if (dependsOn) cell.dependsOn = dependsOn;
237
+ if (Object.keys(metadata).length > 0) cell.metadata = metadata;
238
+ if (raw.output !== void 0) cell.output = raw.output;
239
+ if (raw.error !== void 0) cell.error = String(raw.error);
240
+ return cell;
241
+ }
242
+ function parseWorkbook(content) {
243
+ try {
244
+ if (!content.trim()) return {
245
+ success: false,
246
+ errors: ["Empty workbook content"]
247
+ };
248
+ const errors = [];
249
+ const warnings = [];
250
+ const doc = parseYamlHardened(content);
251
+ if (!isPlainObject(doc)) return {
252
+ success: false,
253
+ errors: ["Workbook must be a YAML mapping"]
254
+ };
255
+ for (const key of findPollutionKeys(doc)) errors.push(`Disallowed key "${key}" (prototype pollution)`);
256
+ const metadata = isPlainObject(doc.metadata) ? doc.metadata : {};
257
+ const workbook = {
258
+ version: doc.version != null ? String(doc.version) : "1.0",
259
+ metadata,
260
+ runtime: normalizeRuntime(doc.runtime),
261
+ cells: []
262
+ };
263
+ if (doc.cells === void 0) warnings.push("Workbook has no cells");
264
+ else if (!Array.isArray(doc.cells)) errors.push("Workbook \"cells\" must be a list");
265
+ else {
266
+ const idSet = /* @__PURE__ */ new Set();
267
+ const cells = doc.cells.map((raw, i) => {
268
+ const cell = mapCell(raw, i, errors);
269
+ if (IDENTIFIER_RE.test(cell.id)) {
270
+ if (idSet.has(cell.id)) errors.push(`Duplicate cell id: "${cell.id}"`);
271
+ else idSet.add(cell.id);
272
+ }
273
+ return cell;
274
+ });
275
+ for (const cell of cells) for (const dep of cell.dependsOn ?? []) if (!idSet.has(dep)) errors.push(`Cell "${cell.id}": depends_on references unknown cell "${dep}"`);
276
+ workbook.cells = cells;
277
+ }
278
+ const success = errors.length === 0;
279
+ return {
280
+ success,
281
+ workbook: success ? workbook : void 0,
282
+ errors: errors.length > 0 ? errors : void 0,
283
+ warnings: warnings.length > 0 ? warnings : void 0
284
+ };
285
+ } catch (error) {
286
+ return {
287
+ success: false,
288
+ errors: [`Parse error: ${error instanceof Error ? error.message : String(error)}`]
289
+ };
290
+ }
291
+ }
292
+ function serializeCell(cell) {
293
+ const out = {
294
+ [cell.type]: cell.content,
295
+ id: cell.id
296
+ };
297
+ if (cell.dependsOn && cell.dependsOn.length > 0) out.depends_on = cell.dependsOn;
298
+ if (cell.metadata) for (const [key, value] of Object.entries(cell.metadata)) {
299
+ if (CELL_TYPE_KEYS.includes(key) || RESERVED_CELL_KEYS.includes(key)) continue;
300
+ out[key] = value;
301
+ }
302
+ if (cell.output !== void 0) out.output = cell.output;
303
+ if (cell.error !== void 0) out.error = String(cell.error);
304
+ return out;
305
+ }
306
+ function serializeWorkbook(workbook) {
307
+ if (!workbook || !Array.isArray(workbook.cells)) throw new Error("serializeWorkbook: invalid workbook (missing \"cells\" array)");
308
+ const runtime = {
309
+ engine: workbook.runtime?.engine ?? "mathts",
310
+ execution: workbook.runtime?.execution ?? "reactive"
311
+ };
312
+ if (workbook.runtime?.timeout !== void 0) runtime.timeout = workbook.runtime.timeout;
313
+ const doc = {
314
+ version: String(workbook.version ?? "1.0"),
315
+ metadata: workbook.metadata ?? {},
316
+ runtime,
317
+ cells: workbook.cells.map(serializeCell)
318
+ };
319
+ return stringify(doc, { lineWidth: 0 });
320
+ }
321
+ function stripOutputs(workbook) {
322
+ return {
323
+ ...workbook,
324
+ cells: workbook.cells.map((cell) => ({
325
+ ...cell,
326
+ output: void 0,
327
+ error: void 0
328
+ }))
329
+ };
330
+ }
331
+ function importWorkbook(input) {
332
+ let obj;
333
+ try {
334
+ obj = parseYamlHardened(input);
335
+ } catch (error) {
336
+ return {
337
+ success: false,
338
+ errors: [`Invalid JSON/YAML: ${error instanceof Error ? error.message : String(error)}`]
339
+ };
340
+ }
341
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) return {
342
+ success: false,
343
+ errors: ["Input must be an object with a \"cells\" array"]
344
+ };
345
+ const pollution = findPollutionKeys(obj);
346
+ if (pollution.length > 0) return {
347
+ success: false,
348
+ errors: [`Disallowed key "${pollution[0]}" (prototype pollution)`]
349
+ };
350
+ const root = obj;
351
+ if (!Array.isArray(root.cells)) return {
352
+ success: false,
353
+ errors: ["Input must have a \"cells\" array"]
354
+ };
355
+ const errors = [];
356
+ const cells = [];
357
+ root.cells.forEach((raw, index) => {
358
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
359
+ errors.push(`Cell at index ${index}: must be an object`);
360
+ return;
361
+ }
362
+ const c = raw;
363
+ const id = typeof c.id === "string" ? c.id : "";
364
+ const type = typeof c.type === "string" ? c.type : "";
365
+ const content = typeof c.content === "string" ? c.content : c.content == null ? "" : String(c.content);
366
+ const dependsOn = (Array.isArray(c.dependsOn) ? c.dependsOn : Array.isArray(c.depends_on) ? c.depends_on : void 0)?.map((d) => String(d));
367
+ const cell = {
368
+ id,
369
+ type,
370
+ content
371
+ };
372
+ if (dependsOn && dependsOn.length > 0) cell.dependsOn = dependsOn;
373
+ cells.push(cell);
374
+ });
375
+ if (errors.length > 0) return {
376
+ success: false,
377
+ errors
378
+ };
379
+ const metadata = root.metadata && typeof root.metadata === "object" && !Array.isArray(root.metadata) ? root.metadata : {};
380
+ const runtime = root.runtime && typeof root.runtime === "object" && !Array.isArray(root.runtime) ? root.runtime : {
381
+ engine: "mathts",
382
+ execution: "reactive"
383
+ };
384
+ const parsed = parseWorkbook(serializeWorkbook({
385
+ version: typeof root.version === "string" ? root.version : "1.0",
386
+ metadata,
387
+ runtime,
388
+ cells
389
+ }));
390
+ if (!parsed.success || !parsed.workbook) return parsed;
391
+ const cycles = detectCycles(buildDependencyGraph(parsed.workbook.cells));
392
+ if (cycles.length > 0) return {
393
+ success: false,
394
+ errors: cycles.map((c) => `Dependency cycle: ${c.join(" -> ")}`)
395
+ };
396
+ return parsed;
397
+ }
398
+ //#endregion
399
+ //#region src/executor.ts
400
+ var WorkbookExecutor = class {
401
+ workbook;
402
+ graph;
403
+ outputs = /* @__PURE__ */ new Map();
404
+ handlers = [];
405
+ constructor(workbook) {
406
+ this.workbook = workbook;
407
+ this.graph = buildDependencyGraph(workbook.cells);
408
+ }
409
+ on(handler) {
410
+ this.handlers.push(handler);
411
+ return () => {
412
+ const index = this.handlers.indexOf(handler);
413
+ if (index >= 0) this.handlers.splice(index, 1);
414
+ };
415
+ }
416
+ emit(event) {
417
+ for (const handler of this.handlers) handler(event);
418
+ }
419
+ async runAll() {
420
+ for (const cellId of this.graph.executionOrder) await this.runCell(cellId);
421
+ this.emit({
422
+ type: "workbook:complete",
423
+ timestamp: Date.now()
424
+ });
425
+ }
426
+ async runCell(cellId) {
427
+ const cell = this.workbook.cells.find((c) => c.id === cellId);
428
+ if (!cell) throw new Error(`Cell not found: ${cellId}`);
429
+ this.emit({
430
+ type: "cell:start",
431
+ cellId,
432
+ timestamp: Date.now()
433
+ });
434
+ try {
435
+ const output = await this.executeCell(cell);
436
+ this.outputs.set(cellId, output);
437
+ this.emit({
438
+ type: "cell:success",
439
+ cellId,
440
+ output,
441
+ timestamp: Date.now()
442
+ });
443
+ if (this.workbook.runtime.execution === "reactive") {
444
+ const dependents = getDependents(this.graph, cellId);
445
+ for (const depId of dependents) this.emit({
446
+ type: "cell:stale",
447
+ cellId: depId,
448
+ timestamp: Date.now()
449
+ });
450
+ }
451
+ return output;
452
+ } catch (error) {
453
+ const errorMessage = error instanceof Error ? error.message : String(error);
454
+ this.emit({
455
+ type: "cell:error",
456
+ cellId,
457
+ error: errorMessage,
458
+ timestamp: Date.now()
459
+ });
460
+ throw error;
461
+ }
462
+ }
463
+ async executeCell(cell) {
464
+ switch (cell.type) {
465
+ case "code": return this.executeCode(cell);
466
+ case "test": return this.executeTest(cell);
467
+ case "markdown":
468
+ case "equation":
469
+ case "visualization": return cell.content;
470
+ case "data": return this.executeData(cell);
471
+ default: throw new Error(`Unsupported cell type: ${cell.type}`);
472
+ }
473
+ }
474
+ buildScope(cell) {
475
+ const scope = {};
476
+ if (cell.dependsOn) {
477
+ for (const depId of cell.dependsOn) if (this.outputs.has(depId)) scope[depId] = this.outputs.get(depId);
478
+ }
479
+ return scope;
480
+ }
481
+ async executeCode(cell) {
482
+ return evaluate(cell.content, this.buildScope(cell));
483
+ }
484
+ async executeTest(cell) {
485
+ const result = evaluate(cell.content, this.buildScope(cell));
486
+ if (typeof result !== "boolean") throw new Error(`test cell must evaluate to a boolean, got ${typeof result}`);
487
+ return result;
488
+ }
489
+ async executeData(cell) {
490
+ return parseYamlSafe(cell.content);
491
+ }
492
+ seedOutputs(entries) {
493
+ this.outputs = new Map(entries);
494
+ }
495
+ async runReport(options = {}) {
496
+ const cells = [];
497
+ const cycles = detectCycles(this.graph);
498
+ if (cycles.length > 0) {
499
+ const description = cycles.map((cycle) => cycle.join(" -> ")).join("; ");
500
+ cells.push({
501
+ id: "(workbook)",
502
+ type: "code",
503
+ status: "error",
504
+ error: `Dependency cycle detected: ${description}`
505
+ });
506
+ return {
507
+ cells,
508
+ ok: false
509
+ };
510
+ }
511
+ let order = this.graph.executionOrder;
512
+ if (options.only !== void 0) {
513
+ const allowed = new Set(getAncestors(this.graph, options.only));
514
+ order = order.filter((id) => allowed.has(id));
515
+ }
516
+ for (const cellId of order) {
517
+ if (options.runIds && !options.runIds.has(cellId)) continue;
518
+ const cell = this.workbook.cells.find((c) => c.id === cellId);
519
+ if (!cell) continue;
520
+ try {
521
+ const output = await this.runCell(cellId);
522
+ if (cell.type === "test") {
523
+ const passed = output === true;
524
+ cells.push({
525
+ id: cell.id,
526
+ type: cell.type,
527
+ status: passed ? "pass" : "fail",
528
+ output,
529
+ error: passed ? void 0 : `Assertion failed in cell '${cell.id}'`
530
+ });
531
+ } else cells.push({
532
+ id: cell.id,
533
+ type: cell.type,
534
+ status: "success",
535
+ output
536
+ });
537
+ } catch (error) {
538
+ cells.push({
539
+ id: cell.id,
540
+ type: cell.type,
541
+ status: "error",
542
+ error: error instanceof Error ? error.message : String(error)
543
+ });
544
+ }
545
+ }
546
+ return {
547
+ cells,
548
+ ok: cells.every((c) => c.status === "success" || c.status === "pass")
549
+ };
550
+ }
551
+ getOutput(cellId) {
552
+ return this.outputs.get(cellId);
553
+ }
554
+ };
555
+ function createExecutor(workbook) {
556
+ return new WorkbookExecutor(workbook);
557
+ }
558
+ //#endregion
559
+ //#region src/formatter.ts
560
+ function bigintReplacer(_key, val) {
561
+ return typeof val === "bigint" ? `${val}n` : val;
562
+ }
563
+ function safeStringify(value) {
564
+ try {
565
+ const json = JSON.stringify(value, bigintReplacer);
566
+ if (json !== void 0) return json;
567
+ } catch {}
568
+ try {
569
+ const seen = /* @__PURE__ */ new WeakSet();
570
+ return JSON.stringify(value, (_key, val) => {
571
+ if (typeof val === "bigint") return `${val}n`;
572
+ if (val !== null && typeof val === "object") {
573
+ if (seen.has(val)) return "[Circular]";
574
+ seen.add(val);
575
+ }
576
+ return val;
577
+ }) ?? "(no result)";
578
+ } catch {
579
+ try {
580
+ return String(value);
581
+ } catch {
582
+ return "[unserializable]";
583
+ }
584
+ }
585
+ }
586
+ function hasCustomToString(value) {
587
+ const toString = value.toString;
588
+ return typeof toString === "function" && toString !== Object.prototype.toString && toString !== Array.prototype.toString;
589
+ }
590
+ function formatResult(value) {
591
+ if (value === null || value === void 0) return "(no result)";
592
+ switch (typeof value) {
593
+ case "string": return value;
594
+ case "number":
595
+ case "boolean": return String(value);
596
+ case "bigint": return `${value}n`;
597
+ case "function": return "(function)";
598
+ case "symbol": return value.toString();
599
+ case "object":
600
+ if (!Array.isArray(value) && hasCustomToString(value)) try {
601
+ const text = value.toString();
602
+ if (text && text !== "[object Object]") return text;
603
+ } catch {}
604
+ return safeStringify(value);
605
+ default: return safeStringify(value);
606
+ }
607
+ }
608
+ //#endregion
609
+ export { topologicalSort as _, detectCellType as a, parseWorkbook as c, buildDependencyGraph as d, detectCycles as f, toMermaid as g, toDOT as h, SUPPORTED_CELL_TYPES as i, serializeWorkbook as l, getDependents as m, WorkbookExecutor as n, importWorkbook as o, getAncestors as p, createExecutor as r, isValidIdentifier as s, formatResult as t, stripOutputs as u, parseYamlSafe as v };
@@ -1,11 +1,19 @@
1
1
  /**
2
2
  * Atomic file write shared by the CLI write commands and the serve session.
3
3
  */
4
+ import { renameSync } from 'node:fs';
5
+ /** Options for tests: the rename to use and how to wait between attempts. */
6
+ export interface AtomicWriteOptions {
7
+ rename?: typeof renameSync;
8
+ sleepMs?: (ms: number) => void;
9
+ }
4
10
  /**
5
11
  * Write `content` to `file` atomically: write a uniquely-named sibling temp
6
12
  * file (exclusive create, so a pre-planted symlink can't be hijacked), then
7
13
  * rename over the target (atomic on the same filesystem; replaces a symlink
8
- * rather than writing through it). On any failure the temp file is removed.
14
+ * rather than writing through it). A rename that fails because another process
15
+ * holds the target open is retried a few times with a growing wait; any other
16
+ * error is thrown at once. On any failure the temp file is removed.
9
17
  */
10
- export declare function writeFileAtomic(file: string, content: string): void;
18
+ export declare function writeFileAtomic(file: string, content: string, options?: AtomicWriteOptions): void;
11
19
  //# sourceMappingURL=fs-atomic.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fs-atomic.d.ts","sourceRoot":"","sources":["../src/fs-atomic.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAanE"}
1
+ {"version":3,"file":"fs-atomic.d.ts","sourceRoot":"","sources":["../src/fs-atomic.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAiB,UAAU,EAAc,MAAM,SAAS,CAAC;AAchE,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,OAAO,UAAU,CAAC;IAC3B,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAMD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,kBAAuB,GAC/B,IAAI,CAwBN"}