|
1 |
| -import type {Variable, VariableDefinition} from "@observablehq/runtime"; |
| 1 | +import {Runtime, type Module, type Variable, type VariableDefinition} from "@observablehq/runtime"; |
2 | 2 | import type {DisplayState} from "./display.js";
|
3 | 3 | import {clear, display, observe} from "./display.js";
|
4 |
| -import {main} from "./index.js"; |
| 4 | +import {library} from "./stdlib/index.js"; |
5 | 5 | import {input} from "./stdlib/generators/index.js";
|
6 | 6 | import {Mutator} from "./stdlib/mutable.js";
|
| 7 | +import {fileAttachments} from "./stdlib/fileAttachment.js"; |
7 | 8 |
|
8 | 9 | export type DefineState = DisplayState & {
|
9 | 10 | /** the runtime variables associated with this cell */
|
@@ -31,61 +32,101 @@ export type Definition = {
|
31 | 32 | assets?: Map<string, string>;
|
32 | 33 | };
|
33 | 34 |
|
34 |
| -export function define(state: DefineState, definition: Definition, observer = observe): void { |
35 |
| - const {id, body, inputs = [], outputs = [], output, autodisplay, autoview, automutable} = definition; |
36 |
| - const variables = state.variables; |
37 |
| - const v = main.variable(observer(state, definition), {shadow: {}}); |
38 |
| - const vid = output ?? (outputs.length ? `cell ${id}` : null); |
39 |
| - if (inputs.includes("display") || inputs.includes("view")) { |
40 |
| - let displayVersion = -1; // the variable._version of currently-displayed values |
41 |
| - const vd = new (v.constructor as typeof Variable)(2, v._module); |
42 |
| - vd.define( |
43 |
| - inputs.filter((i) => i !== "display" && i !== "view"), |
44 |
| - () => { |
45 |
| - const version = v._version; // capture version on input change |
46 |
| - return (value: unknown) => { |
47 |
| - if (version < displayVersion) throw new Error("stale display"); |
48 |
| - else if (state.variables[0] !== v) throw new Error("stale display"); |
49 |
| - else if (version > displayVersion) clear(state); |
50 |
| - displayVersion = version; |
51 |
| - display(state, value); |
52 |
| - return value; |
53 |
| - }; |
54 |
| - } |
55 |
| - ); |
56 |
| - v._shadow.set("display", vd); |
57 |
| - if (inputs.includes("view")) { |
58 |
| - const vv = new (v.constructor as typeof Variable)(2, v._module, null, {shadow: {}}); |
59 |
| - vv._shadow.set("display", vd); |
60 |
| - vv.define(["display"], (display) => (value: unknown) => input(display(value))); |
61 |
| - v._shadow.set("view", vv); |
| 35 | +export class NotebookInstance { |
| 36 | + |
| 37 | + protected runtime = Object.assign(new Runtime({ ...library, __ojs_runtime: () => this.runtime }), { fileAttachments }); |
| 38 | + protected main = (this.runtime as typeof this.runtime & { main: Module }).main = this.runtime.module(); |
| 39 | + protected stateById = new Map<number, DefineState>(); |
| 40 | + |
| 41 | + constructor() { |
| 42 | + this.main.constructor.prototype.defines = function (this: Module, name: string): boolean { |
| 43 | + return ( |
| 44 | + this._scope.has(name) || |
| 45 | + this._builtins.has(name) || |
| 46 | + this._runtime._builtin._scope.has(name) |
| 47 | + ); |
| 48 | + }; |
| 49 | + } |
| 50 | + |
| 51 | + define(state: DefineState, definition: Definition, observer = observe): void { |
| 52 | + this.undefine(definition.id); |
| 53 | + this._define(state, definition, observer); |
| 54 | + this.stateById.set(definition.id, state); |
| 55 | + } |
| 56 | + |
| 57 | + undefine(id: number): void { |
| 58 | + const state = this.stateById.get(id); |
| 59 | + if (state) { |
| 60 | + state.variables.forEach((v) => v.delete()); |
| 61 | + this.stateById.delete(id); |
| 62 | + state.root.textContent = ""; |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + clear(): void { |
| 67 | + const keys = Array.from(this.stateById.keys()); |
| 68 | + for (const key of keys) { |
| 69 | + this.undefine(key); |
62 | 70 | }
|
63 |
| - } else if (!autodisplay) { |
64 |
| - clear(state); |
65 | 71 | }
|
66 |
| - variables.push(v.define(vid, inputs, body)); |
67 |
| - if (output != null) { |
68 |
| - if (autoview) { |
69 |
| - const o = unprefix(output, "viewof$"); |
70 |
| - variables.push(main.define(o, [output], input)); |
71 |
| - } else if (automutable) { |
72 |
| - const o = unprefix(output, "mutable "); |
73 |
| - const x = `cell ${id}`; |
74 |
| - v.define(o, [x], ([mutable]) => mutable); // observe live value |
75 |
| - variables.push( |
76 |
| - main.define(output, inputs, body), // initial value |
77 |
| - main.define(x, [output], Mutator), |
78 |
| - main.define(`mutable$${o}`, [x], ([, mutator]) => mutator) |
| 72 | + |
| 73 | + protected _define(state: DefineState, definition: Definition, observer = observe): void { |
| 74 | + const { id, body, inputs = [], outputs = [], output, autodisplay, autoview, automutable } = definition; |
| 75 | + const variables = state.variables; |
| 76 | + const v = this.main.variable(observer(state, definition), { shadow: {} }); |
| 77 | + const vid = output ?? (outputs.length ? `cell ${id}` : null); |
| 78 | + if (inputs.includes("display") || inputs.includes("view")) { |
| 79 | + let displayVersion = -1; // the variable._version of currently-displayed values |
| 80 | + const vd = new (v.constructor as typeof Variable)(2, v._module); |
| 81 | + vd.define( |
| 82 | + inputs.filter((i) => i !== "display" && i !== "view"), |
| 83 | + () => { |
| 84 | + const version = v._version; // capture version on input change |
| 85 | + return (value: unknown) => { |
| 86 | + if (version < displayVersion) throw new Error("stale display"); |
| 87 | + else if (state.variables[0] !== v) throw new Error("stale display"); |
| 88 | + else if (version > displayVersion) clear(state); |
| 89 | + displayVersion = version; |
| 90 | + display(state, value); |
| 91 | + return value; |
| 92 | + }; |
| 93 | + } |
79 | 94 | );
|
| 95 | + v._shadow.set("display", vd); |
| 96 | + if (inputs.includes("view")) { |
| 97 | + const vv = new (v.constructor as typeof Variable)(2, v._module, null, { shadow: {} }); |
| 98 | + vv._shadow.set("display", vd); |
| 99 | + vv.define(["display"], (display) => (value: unknown) => input(display(value))); |
| 100 | + v._shadow.set("view", vv); |
| 101 | + } |
| 102 | + } else if (!autodisplay) { |
| 103 | + clear(state); |
80 | 104 | }
|
81 |
| - } else { |
82 |
| - for (const o of outputs) { |
83 |
| - variables.push(main.variable(true).define(o, [vid!], (exports) => exports[o])); |
| 105 | + variables.push(v.define(vid, inputs, body)); |
| 106 | + if (output != null) { |
| 107 | + if (autoview) { |
| 108 | + const o = this.unprefix(output, "viewof$"); |
| 109 | + variables.push(this.main.define(o, [output], input)); |
| 110 | + } else if (automutable) { |
| 111 | + const o = this.unprefix(output, "mutable "); |
| 112 | + const x = `cell ${id}`; |
| 113 | + v.define(o, [x], ([mutable]) => mutable); // observe live value |
| 114 | + variables.push( |
| 115 | + this.main.define(output, inputs, body), // initial value |
| 116 | + this.main.define(x, [output], Mutator), |
| 117 | + this.main.define(`mutable$${o}`, [x], ([, mutator]) => mutator) |
| 118 | + ); |
| 119 | + } |
| 120 | + } else { |
| 121 | + for (const o of outputs) { |
| 122 | + variables.push(this.main.variable(true).define(o, [vid!], (exports) => exports[o])); |
| 123 | + } |
84 | 124 | }
|
85 | 125 | }
|
86 |
| -} |
87 | 126 |
|
88 |
| -function unprefix(name: string, prefix: string): string { |
89 |
| - if (!name.startsWith(prefix)) throw new Error(`expected ${prefix}: ${name}`); |
90 |
| - return name.slice(prefix.length); |
| 127 | + protected unprefix(name: string, prefix: string): string { |
| 128 | + if (!name.startsWith(prefix)) throw new Error(`expected ${prefix}: ${name}`); |
| 129 | + return name.slice(prefix.length); |
| 130 | + } |
91 | 131 | }
|
| 132 | + |
0 commit comments