@ebowwa/sandbox 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compilers/index.d.ts +24 -0
- package/dist/compilers/index.d.ts.map +1 -0
- package/dist/compilers/index.js +42 -0
- package/dist/compilers/index.js.map +1 -0
- package/dist/compilers/javascript.d.ts +117 -0
- package/dist/compilers/javascript.d.ts.map +1 -0
- package/dist/compilers/javascript.js +462 -0
- package/dist/compilers/javascript.js.map +1 -0
- package/dist/compilers/python.d.ts +140 -0
- package/dist/compilers/python.d.ts.map +1 -0
- package/dist/compilers/python.js +650 -0
- package/dist/compilers/python.js.map +1 -0
- package/dist/compilers/typescript.d.ts +99 -0
- package/dist/compilers/typescript.d.ts.map +1 -0
- package/dist/compilers/typescript.js +323 -0
- package/dist/compilers/typescript.js.map +1 -0
- package/dist/core/cell.d.ts +160 -0
- package/dist/core/cell.d.ts.map +1 -0
- package/dist/core/cell.js +319 -0
- package/dist/core/cell.js.map +1 -0
- package/dist/core/compiler.d.ts +126 -0
- package/dist/core/compiler.d.ts.map +1 -0
- package/dist/core/compiler.js +123 -0
- package/dist/core/compiler.js.map +1 -0
- package/dist/core/index.d.ts +19 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/limits.d.ts +173 -0
- package/dist/core/limits.d.ts.map +1 -0
- package/dist/core/limits.js +440 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/permissions.d.ts +103 -0
- package/dist/core/permissions.d.ts.map +1 -0
- package/dist/core/permissions.js +341 -0
- package/dist/core/permissions.js.map +1 -0
- package/dist/core/runtime.d.ts +127 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +325 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/types.d.ts +380 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +67 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +279 -0
- package/dist/index.js.map +1 -0
- package/dist/multi/index.d.ts +9 -0
- package/dist/multi/index.d.ts.map +1 -0
- package/dist/multi/index.js +7 -0
- package/dist/multi/index.js.map +1 -0
- package/dist/multi/polyglot.d.ts +179 -0
- package/dist/multi/polyglot.d.ts.map +1 -0
- package/dist/multi/polyglot.js +319 -0
- package/dist/multi/polyglot.js.map +1 -0
- package/dist/runtimes/docker.d.ts +97 -0
- package/dist/runtimes/docker.d.ts.map +1 -0
- package/dist/runtimes/docker.js +368 -0
- package/dist/runtimes/docker.js.map +1 -0
- package/dist/runtimes/index.d.ts +11 -0
- package/dist/runtimes/index.d.ts.map +1 -0
- package/dist/runtimes/index.js +9 -0
- package/dist/runtimes/index.js.map +1 -0
- package/dist/runtimes/process.d.ts +47 -0
- package/dist/runtimes/process.d.ts.map +1 -0
- package/dist/runtimes/process.js +230 -0
- package/dist/runtimes/process.js.map +1 -0
- package/dist/session/index.d.ts +12 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +9 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/kernel.d.ts +199 -0
- package/dist/session/kernel.d.ts.map +1 -0
- package/dist/session/kernel.js +400 -0
- package/dist/session/kernel.js.map +1 -0
- package/dist/session/notebook.d.ts +168 -0
- package/dist/session/notebook.d.ts.map +1 -0
- package/dist/session/notebook.js +499 -0
- package/dist/session/notebook.js.map +1 -0
- package/dist/session/repl.d.ts +159 -0
- package/dist/session/repl.d.ts.map +1 -0
- package/dist/session/repl.js +409 -0
- package/dist/session/repl.js.map +1 -0
- package/package.json +142 -0
- package/src/compilers/index.ts +80 -0
- package/src/compilers/javascript.ts +571 -0
- package/src/compilers/python.ts +785 -0
- package/src/compilers/typescript.ts +442 -0
- package/src/core/cell.ts +439 -0
- package/src/core/compiler.ts +250 -0
- package/src/core/index.ts +123 -0
- package/src/core/limits.ts +508 -0
- package/src/core/permissions.ts +409 -0
- package/src/core/runtime.ts +499 -0
- package/src/core/types.ts +528 -0
- package/src/global.d.ts +59 -0
- package/src/index.ts +515 -0
- package/src/multi/index.ts +22 -0
- package/src/multi/polyglot.ts +461 -0
- package/src/runtimes/docker.ts +501 -0
- package/src/runtimes/index.ts +21 -0
- package/src/runtimes/process.ts +316 -0
- package/src/session/index.ts +41 -0
- package/src/session/kernel.ts +553 -0
- package/src/session/notebook.ts +635 -0
- package/src/session/repl.ts +521 -0
- package/src/wasm2wasm.d.ts +35 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notebook Primitive
|
|
3
|
+
*
|
|
4
|
+
* Multi-cell, multi-language document with sequential execution.
|
|
5
|
+
* Jupyter-style notebook with cell dependencies and state sharing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
Language,
|
|
10
|
+
Notebook,
|
|
11
|
+
NotebookMetadata,
|
|
12
|
+
NotebookRunOptions,
|
|
13
|
+
NotebookResult,
|
|
14
|
+
Cell,
|
|
15
|
+
ExecutionResult,
|
|
16
|
+
ExecutionOptions,
|
|
17
|
+
Permissions,
|
|
18
|
+
Limits,
|
|
19
|
+
} from "../core/types.js";
|
|
20
|
+
import { CellRegistry, CellBuilder } from "../core/cell.js";
|
|
21
|
+
import type { ExecutableCell, CellState } from "../core/cell.js";
|
|
22
|
+
import { BaseKernel, KernelManager } from "./kernel.js";
|
|
23
|
+
import type { KernelOptions } from "./kernel.js";
|
|
24
|
+
import { createPermissions } from "../core/permissions.js";
|
|
25
|
+
import { v4 as uuid } from "uuid";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Notebook options
|
|
29
|
+
*/
|
|
30
|
+
export interface NotebookOptions {
|
|
31
|
+
/** Notebook ID */
|
|
32
|
+
id?: string;
|
|
33
|
+
/** Notebook name */
|
|
34
|
+
name?: string;
|
|
35
|
+
/** Default language */
|
|
36
|
+
defaultLanguage?: Language;
|
|
37
|
+
/** Initial cells */
|
|
38
|
+
cells?: Cell[];
|
|
39
|
+
/** Permissions */
|
|
40
|
+
permissions?: Permissions;
|
|
41
|
+
/** Resource limits */
|
|
42
|
+
limits?: Limits;
|
|
43
|
+
/** Kernel manager to use */
|
|
44
|
+
kernelManager?: KernelManager;
|
|
45
|
+
/** Kernel factory */
|
|
46
|
+
kernelFactory?: (language: Language) => Promise<BaseKernel>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Notebook state
|
|
51
|
+
*/
|
|
52
|
+
export type NotebookState =
|
|
53
|
+
| "idle"
|
|
54
|
+
| "running"
|
|
55
|
+
| "error"
|
|
56
|
+
| "cancelled";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Notebook events
|
|
60
|
+
*/
|
|
61
|
+
export interface NotebookEvents {
|
|
62
|
+
onCellStart?: (cellId: string, cell: ExecutableCell) => void;
|
|
63
|
+
onCellComplete?: (cellId: string, result: ExecutionResult) => void;
|
|
64
|
+
onCellError?: (cellId: string, error: Error) => void;
|
|
65
|
+
onNotebookComplete?: (results: NotebookResult[]) => void;
|
|
66
|
+
onNotebookError?: (error: Error) => void;
|
|
67
|
+
onProgress?: (completed: number, total: number) => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Base Notebook Implementation
|
|
72
|
+
*/
|
|
73
|
+
export class BaseNotebook implements Notebook {
|
|
74
|
+
readonly id: string;
|
|
75
|
+
readonly metadata: NotebookMetadata;
|
|
76
|
+
|
|
77
|
+
private registry: CellRegistry;
|
|
78
|
+
private kernelManager: KernelManager;
|
|
79
|
+
private kernelFactory?: (language: Language) => Promise<BaseKernel>;
|
|
80
|
+
private kernels = new Map<Language, BaseKernel>();
|
|
81
|
+
private permissions: Permissions;
|
|
82
|
+
private limits: Limits;
|
|
83
|
+
private state: NotebookState = "idle";
|
|
84
|
+
private executionCount = 0;
|
|
85
|
+
private events: NotebookEvents = {};
|
|
86
|
+
private abortController?: AbortController;
|
|
87
|
+
|
|
88
|
+
constructor(options: NotebookOptions) {
|
|
89
|
+
this.id = options.id ?? uuid();
|
|
90
|
+
this.registry = new CellRegistry();
|
|
91
|
+
this.kernelManager = options.kernelManager ?? new KernelManager();
|
|
92
|
+
this.kernelFactory = options.kernelFactory;
|
|
93
|
+
this.permissions = options.permissions ?? {};
|
|
94
|
+
this.limits = options.limits ?? {};
|
|
95
|
+
|
|
96
|
+
this.metadata = {
|
|
97
|
+
name: options.name ?? "Untitled",
|
|
98
|
+
language: options.defaultLanguage ?? "javascript",
|
|
99
|
+
created: new Date(),
|
|
100
|
+
modified: new Date(),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Add initial cells
|
|
104
|
+
if (options.cells) {
|
|
105
|
+
for (const cell of options.cells) {
|
|
106
|
+
this.addCell(cell);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get current state
|
|
113
|
+
*/
|
|
114
|
+
getState(): NotebookState {
|
|
115
|
+
return this.state;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Set event handlers
|
|
120
|
+
*/
|
|
121
|
+
setEvents(events: NotebookEvents): void {
|
|
122
|
+
this.events = { ...this.events, ...events };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get all cells
|
|
127
|
+
*/
|
|
128
|
+
get cells(): ExecutableCell[] {
|
|
129
|
+
return this.registry.getAll();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Add cell
|
|
134
|
+
*/
|
|
135
|
+
addCell(cell: Omit<Cell, "id">): Cell {
|
|
136
|
+
const executableCell: ExecutableCell = {
|
|
137
|
+
...cell,
|
|
138
|
+
id: uuid(),
|
|
139
|
+
state: "idle",
|
|
140
|
+
metadata: {
|
|
141
|
+
...cell.metadata,
|
|
142
|
+
index: this.registry.count,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
this.registry.add(executableCell);
|
|
147
|
+
this.metadata.modified = new Date();
|
|
148
|
+
|
|
149
|
+
return executableCell;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Remove cell
|
|
154
|
+
*/
|
|
155
|
+
removeCell(cellId: string): void {
|
|
156
|
+
this.registry.remove(cellId);
|
|
157
|
+
this.metadata.modified = new Date();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Move cell
|
|
162
|
+
*/
|
|
163
|
+
moveCell(cellId: string, newIndex: number): void {
|
|
164
|
+
const cell = this.registry.get(cellId);
|
|
165
|
+
if (!cell) return;
|
|
166
|
+
|
|
167
|
+
cell.metadata = { ...cell.metadata, index: newIndex };
|
|
168
|
+
this.metadata.modified = new Date();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get cell by ID
|
|
173
|
+
*/
|
|
174
|
+
getCell(cellId: string): Cell | undefined {
|
|
175
|
+
return this.registry.get(cellId);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Update cell source
|
|
180
|
+
*/
|
|
181
|
+
updateCell(cellId: string, source: string): void {
|
|
182
|
+
const cell = this.registry.get(cellId);
|
|
183
|
+
if (!cell) return;
|
|
184
|
+
|
|
185
|
+
cell.source = source;
|
|
186
|
+
cell.state = "idle"; // Reset state on edit
|
|
187
|
+
cell.result = undefined;
|
|
188
|
+
this.metadata.modified = new Date();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get or create kernel for language
|
|
193
|
+
*/
|
|
194
|
+
private async getKernel(language: Language): Promise<BaseKernel> {
|
|
195
|
+
let kernel = this.kernels.get(language);
|
|
196
|
+
|
|
197
|
+
if (!kernel) {
|
|
198
|
+
if (this.kernelFactory) {
|
|
199
|
+
kernel = await this.kernelFactory(language);
|
|
200
|
+
} else {
|
|
201
|
+
// Create a basic kernel - would need proper compiler/runtime
|
|
202
|
+
throw new Error(`No kernel available for language: ${language}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.kernels.set(language, kernel);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return kernel;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Execute all cells
|
|
213
|
+
*/
|
|
214
|
+
async runAll(options?: NotebookRunOptions): Promise<NotebookResult[]> {
|
|
215
|
+
if (this.state === "running") {
|
|
216
|
+
throw new Error("Notebook is already running");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.state = "running";
|
|
220
|
+
this.abortController = new AbortController();
|
|
221
|
+
const results: NotebookResult[] = [];
|
|
222
|
+
const executionOrder = this.registry.getExecutionOrder();
|
|
223
|
+
const total = executionOrder.length;
|
|
224
|
+
let completed = 0;
|
|
225
|
+
|
|
226
|
+
// Find start index
|
|
227
|
+
let startIndex = 0;
|
|
228
|
+
if (options?.continueFrom) {
|
|
229
|
+
startIndex = executionOrder.indexOf(options.continueFrom);
|
|
230
|
+
if (startIndex === -1) startIndex = 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
for (let i = startIndex; i < executionOrder.length; i++) {
|
|
235
|
+
// Check for abort
|
|
236
|
+
if (this.abortController.signal.aborted) {
|
|
237
|
+
this.state = "cancelled";
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const cellId = executionOrder[i];
|
|
242
|
+
const result = await this.runCell(cellId, {
|
|
243
|
+
signal: this.abortController.signal,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
results.push(result);
|
|
247
|
+
completed++;
|
|
248
|
+
this.events.onProgress?.(completed, total);
|
|
249
|
+
|
|
250
|
+
// Stop on error
|
|
251
|
+
if (!result.result.success && options?.stopOnError !== false) {
|
|
252
|
+
this.state = "error";
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (this.state === "running") {
|
|
258
|
+
this.state = "idle";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.events.onNotebookComplete?.(results);
|
|
262
|
+
return results;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
this.state = "error";
|
|
265
|
+
this.events.onNotebookError?.(error instanceof Error ? error : new Error(String(error)));
|
|
266
|
+
throw error;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Execute single cell
|
|
272
|
+
*/
|
|
273
|
+
async runCell(
|
|
274
|
+
cellId: string,
|
|
275
|
+
options?: ExecutionOptions
|
|
276
|
+
): Promise<NotebookResult> {
|
|
277
|
+
const cell = this.registry.get(cellId);
|
|
278
|
+
if (!cell) {
|
|
279
|
+
throw new Error(`Cell not found: ${cellId}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
cell.state = "queued";
|
|
283
|
+
this.events.onCellStart?.(cellId, cell);
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
// Get kernel for language
|
|
287
|
+
const kernel = await this.getKernel(cell.language);
|
|
288
|
+
|
|
289
|
+
// Execute
|
|
290
|
+
cell.state = "running";
|
|
291
|
+
const result = await kernel.execute(cell.source, options);
|
|
292
|
+
|
|
293
|
+
// Update cell
|
|
294
|
+
cell.result = result;
|
|
295
|
+
cell.state = result.success ? "success" : "error";
|
|
296
|
+
cell.executionCount = ++this.executionCount;
|
|
297
|
+
cell.lastRun = new Date();
|
|
298
|
+
|
|
299
|
+
this.events.onCellComplete?.(cellId, result);
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
cellId,
|
|
303
|
+
executionCount: cell.executionCount,
|
|
304
|
+
result,
|
|
305
|
+
timestamp: new Date(),
|
|
306
|
+
};
|
|
307
|
+
} catch (error) {
|
|
308
|
+
cell.state = "error";
|
|
309
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
310
|
+
this.events.onCellError?.(cellId, err);
|
|
311
|
+
|
|
312
|
+
const result: ExecutionResult = {
|
|
313
|
+
success: false,
|
|
314
|
+
error: {
|
|
315
|
+
message: err.message,
|
|
316
|
+
type: "runtime",
|
|
317
|
+
},
|
|
318
|
+
metrics: { duration: 0, memoryUsed: 0 },
|
|
319
|
+
output: { stdout: [], stderr: [], displays: [] },
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
cell.result = result;
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
cellId,
|
|
326
|
+
executionCount: ++this.executionCount,
|
|
327
|
+
result,
|
|
328
|
+
timestamp: new Date(),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Execute cells from index
|
|
335
|
+
*/
|
|
336
|
+
async runFrom(startIndex: number): Promise<NotebookResult[]> {
|
|
337
|
+
const cells = this.registry
|
|
338
|
+
.getAll()
|
|
339
|
+
.sort((a, b) => ((a.metadata?.index as number) ?? 0) - ((b.metadata?.index as number) ?? 0));
|
|
340
|
+
|
|
341
|
+
if (startIndex >= cells.length) {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const startCell = cells[startIndex];
|
|
346
|
+
return this.runAll({ continueFrom: startCell.id });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Run cells that depend on a changed cell
|
|
351
|
+
*/
|
|
352
|
+
async runDependents(cellId: string): Promise<NotebookResult[]> {
|
|
353
|
+
const toReexecute = this.registry.getCellsToReexecute(cellId);
|
|
354
|
+
const results: NotebookResult[] = [];
|
|
355
|
+
|
|
356
|
+
for (const depId of toReexecute) {
|
|
357
|
+
const result = await this.runCell(depId);
|
|
358
|
+
results.push(result);
|
|
359
|
+
|
|
360
|
+
if (!result.result.success) {
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return results;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Cancel execution
|
|
370
|
+
*/
|
|
371
|
+
cancel(): void {
|
|
372
|
+
if (this.abortController) {
|
|
373
|
+
this.abortController.abort();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Clear all cell outputs
|
|
379
|
+
*/
|
|
380
|
+
clearOutputs(): void {
|
|
381
|
+
for (const cell of this.registry.getAll()) {
|
|
382
|
+
cell.result = undefined;
|
|
383
|
+
cell.state = "idle";
|
|
384
|
+
}
|
|
385
|
+
this.executionCount = 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Restart all kernels
|
|
390
|
+
*/
|
|
391
|
+
async restart(): Promise<void> {
|
|
392
|
+
for (const kernel of this.kernels.values()) {
|
|
393
|
+
await kernel.restart();
|
|
394
|
+
}
|
|
395
|
+
this.clearOutputs();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Export notebook
|
|
400
|
+
*/
|
|
401
|
+
export(format: "ipynb" | "json" | "markdown"): string {
|
|
402
|
+
switch (format) {
|
|
403
|
+
case "ipynb":
|
|
404
|
+
return this.toIpynb();
|
|
405
|
+
case "json":
|
|
406
|
+
return this.toJson();
|
|
407
|
+
case "markdown":
|
|
408
|
+
return this.toMarkdown();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Import notebook
|
|
414
|
+
*/
|
|
415
|
+
static import(data: string, format: "ipynb" | "json" | "markdown"): BaseNotebook {
|
|
416
|
+
switch (format) {
|
|
417
|
+
case "ipynb":
|
|
418
|
+
return BaseNotebook.fromIpynb(data);
|
|
419
|
+
case "json":
|
|
420
|
+
return BaseNotebook.fromJson(data);
|
|
421
|
+
case "markdown":
|
|
422
|
+
return BaseNotebook.fromMarkdown(data);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Export to Jupyter format
|
|
428
|
+
*/
|
|
429
|
+
private toIpynb(): string {
|
|
430
|
+
const cells = this.registry.getAll().sort(
|
|
431
|
+
(a, b) => ((a.metadata?.index as number) ?? 0) - ((b.metadata?.index as number) ?? 0)
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
const ipynb = {
|
|
435
|
+
nbformat: 4,
|
|
436
|
+
nbformat_minor: 5,
|
|
437
|
+
metadata: {
|
|
438
|
+
kernelspec: {
|
|
439
|
+
display_name: this.metadata.name,
|
|
440
|
+
language: this.metadata.language,
|
|
441
|
+
name: this.metadata.language,
|
|
442
|
+
},
|
|
443
|
+
language_info: {
|
|
444
|
+
name: this.metadata.language,
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
cells: cells.map(cell => ({
|
|
448
|
+
id: cell.id,
|
|
449
|
+
cell_type: "code",
|
|
450
|
+
metadata: cell.metadata ?? {},
|
|
451
|
+
source: cell.source.split("\n"),
|
|
452
|
+
execution_count: cell.executionCount ?? null,
|
|
453
|
+
outputs: this.cellToOutputs(cell),
|
|
454
|
+
})),
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
return JSON.stringify(ipynb, null, 2);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Export to JSON
|
|
462
|
+
*/
|
|
463
|
+
private toJson(): string {
|
|
464
|
+
return JSON.stringify({
|
|
465
|
+
id: this.id,
|
|
466
|
+
metadata: this.metadata,
|
|
467
|
+
cells: this.registry.getAll(),
|
|
468
|
+
}, null, 2);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Export to Markdown
|
|
473
|
+
*/
|
|
474
|
+
private toMarkdown(): string {
|
|
475
|
+
const cells = this.registry.getAll().sort(
|
|
476
|
+
(a, b) => ((a.metadata?.index as number) ?? 0) - ((b.metadata?.index as number) ?? 0)
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
const lines: string[] = [
|
|
480
|
+
`# ${this.metadata.name}`,
|
|
481
|
+
"",
|
|
482
|
+
`> Language: ${this.metadata.language}`,
|
|
483
|
+
`> Created: ${this.metadata.created.toISOString()}`,
|
|
484
|
+
"",
|
|
485
|
+
];
|
|
486
|
+
|
|
487
|
+
for (const cell of cells) {
|
|
488
|
+
const lang = cell.language === "javascript" ? "js" : cell.language;
|
|
489
|
+
lines.push(`\`\`\`${lang}`);
|
|
490
|
+
lines.push(cell.source);
|
|
491
|
+
lines.push("```");
|
|
492
|
+
lines.push("");
|
|
493
|
+
|
|
494
|
+
if (cell.result) {
|
|
495
|
+
if (cell.result.output.stdout.length > 0) {
|
|
496
|
+
lines.push("**Output:**");
|
|
497
|
+
lines.push("```");
|
|
498
|
+
lines.push(...cell.result.output.stdout);
|
|
499
|
+
lines.push("```");
|
|
500
|
+
lines.push("");
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return lines.join("\n");
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Import from Jupyter format
|
|
510
|
+
*/
|
|
511
|
+
static fromIpynb(data: string): BaseNotebook {
|
|
512
|
+
const ipynb = JSON.parse(data);
|
|
513
|
+
const cells: Cell[] = [];
|
|
514
|
+
|
|
515
|
+
for (const cell of ipynb.cells ?? []) {
|
|
516
|
+
if (cell.cell_type === "code") {
|
|
517
|
+
cells.push({
|
|
518
|
+
id: cell.id ?? uuid(),
|
|
519
|
+
language: (ipynb.metadata?.language_info?.name as Language) ?? "python",
|
|
520
|
+
source: Array.isArray(cell.source)
|
|
521
|
+
? cell.source.join("\n")
|
|
522
|
+
: cell.source,
|
|
523
|
+
metadata: cell.metadata,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return new BaseNotebook({
|
|
529
|
+
name: ipynb.metadata?.kernelspec?.display_name ?? "Imported Notebook",
|
|
530
|
+
defaultLanguage: ipynb.metadata?.language_info?.name,
|
|
531
|
+
cells,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Import from JSON
|
|
537
|
+
*/
|
|
538
|
+
static fromJson(data: string): BaseNotebook {
|
|
539
|
+
const obj = JSON.parse(data);
|
|
540
|
+
|
|
541
|
+
return new BaseNotebook({
|
|
542
|
+
id: obj.id,
|
|
543
|
+
name: obj.metadata?.name,
|
|
544
|
+
defaultLanguage: obj.metadata?.language,
|
|
545
|
+
cells: obj.cells,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Import from Markdown
|
|
551
|
+
*/
|
|
552
|
+
static fromMarkdown(data: string): BaseNotebook {
|
|
553
|
+
const cells: ExecutableCell[] = [];
|
|
554
|
+
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
555
|
+
let match;
|
|
556
|
+
let index = 0;
|
|
557
|
+
|
|
558
|
+
// Extract title
|
|
559
|
+
const titleMatch = data.match(/^#\s+(.+)$/m);
|
|
560
|
+
const name = titleMatch ? titleMatch[1] : "Imported Notebook";
|
|
561
|
+
|
|
562
|
+
while ((match = codeBlockRegex.exec(data)) !== null) {
|
|
563
|
+
const lang = (match[1] as Language) ?? "javascript";
|
|
564
|
+
const source = match[2].trim();
|
|
565
|
+
|
|
566
|
+
cells.push({
|
|
567
|
+
id: uuid(),
|
|
568
|
+
language: lang,
|
|
569
|
+
source,
|
|
570
|
+
state: "idle",
|
|
571
|
+
metadata: { index: index++ },
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const notebook = new BaseNotebook({ name, cells });
|
|
576
|
+
return notebook;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Convert cell outputs to Jupyter format
|
|
581
|
+
*/
|
|
582
|
+
private cellToOutputs(cell: ExecutableCell): unknown[] {
|
|
583
|
+
const outputs: unknown[] = [];
|
|
584
|
+
|
|
585
|
+
if (!cell.result) return outputs;
|
|
586
|
+
|
|
587
|
+
// stdout
|
|
588
|
+
if (cell.result.output.stdout.length > 0) {
|
|
589
|
+
outputs.push({
|
|
590
|
+
output_type: "stream",
|
|
591
|
+
name: "stdout",
|
|
592
|
+
text: cell.result.output.stdout,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// stderr
|
|
597
|
+
if (cell.result.output.stderr.length > 0) {
|
|
598
|
+
outputs.push({
|
|
599
|
+
output_type: "stream",
|
|
600
|
+
name: "stderr",
|
|
601
|
+
text: cell.result.output.stderr,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// displays
|
|
606
|
+
for (const display of cell.result.output.displays) {
|
|
607
|
+
outputs.push({
|
|
608
|
+
output_type: "execute_result" as const,
|
|
609
|
+
data: {
|
|
610
|
+
[`text/${display.type}`]: display.data,
|
|
611
|
+
},
|
|
612
|
+
execution_count: cell.executionCount,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// error
|
|
617
|
+
if (cell.result.error) {
|
|
618
|
+
outputs.push({
|
|
619
|
+
output_type: "error",
|
|
620
|
+
ename: cell.result.error.type,
|
|
621
|
+
evalue: cell.result.error.message,
|
|
622
|
+
traceback: [cell.result.error.stack ?? ""],
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return outputs;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Create a notebook
|
|
632
|
+
*/
|
|
633
|
+
export function createNotebook(options: NotebookOptions): BaseNotebook {
|
|
634
|
+
return new BaseNotebook(options);
|
|
635
|
+
}
|