diff --git a/src/runtime/define.ts b/src/runtime/define.ts index d1a6cbe..bbc25ad 100644 --- a/src/runtime/define.ts +++ b/src/runtime/define.ts @@ -1,9 +1,10 @@ -import type {Variable, VariableDefinition} from "@observablehq/runtime"; +import {Runtime, type Module, type Variable, type VariableDefinition} from "@observablehq/runtime"; import type {DisplayState} from "./display.js"; import {clear, display, observe} from "./display.js"; -import {main} from "./index.js"; +import {library} from "./stdlib/index.js"; import {input} from "./stdlib/generators/index.js"; import {Mutator} from "./stdlib/mutable.js"; +import {fileAttachments} from "./stdlib/fileAttachment.js"; export type DefineState = DisplayState & { /** the runtime variables associated with this cell */ @@ -31,61 +32,87 @@ export type Definition = { assets?: Map; }; -export function define(state: DefineState, definition: Definition, observer = observe): void { - const {id, body, inputs = [], outputs = [], output, autodisplay, autoview, automutable} = definition; - const variables = state.variables; - const v = main.variable(observer(state, definition), {shadow: {}}); - const vid = output ?? (outputs.length ? `cell ${id}` : null); - if (inputs.includes("display") || inputs.includes("view")) { - let displayVersion = -1; // the variable._version of currently-displayed values - const vd = new (v.constructor as typeof Variable)(2, v._module); - vd.define( - inputs.filter((i) => i !== "display" && i !== "view"), - () => { - const version = v._version; // capture version on input change - return (value: unknown) => { - if (version < displayVersion) throw new Error("stale display"); - else if (state.variables[0] !== v) throw new Error("stale display"); - else if (version > displayVersion) clear(state); - displayVersion = version; - display(state, value); - return value; - }; - } - ); - v._shadow.set("display", vd); - if (inputs.includes("view")) { - const vv = new (v.constructor as typeof Variable)(2, v._module, null, {shadow: {}}); - vv._shadow.set("display", vd); - vv.define(["display"], (display) => (value: unknown) => input(display(value))); - v._shadow.set("view", vv); - } - } else if (!autodisplay) { - clear(state); +export class NotebookInstance { + + readonly runtime = Object.assign(new Runtime({ ...library, __ojs_runtime: () => this.runtime }), { fileAttachments }); + readonly main = (this.runtime as typeof this.runtime & { main: Module }).main = this.runtime.module(); + + constructor() { } - variables.push(v.define(vid, inputs, body)); - if (output != null) { - if (autoview) { - const o = unprefix(output, "viewof$"); - variables.push(main.define(o, [output], input)); - } else if (automutable) { - const o = unprefix(output, "mutable "); - const x = `cell ${id}`; - v.define(o, [x], ([mutable]) => mutable); // observe live value - variables.push( - main.define(output, inputs, body), // initial value - main.define(x, [output], Mutator), - main.define(`mutable$${o}`, [x], ([, mutator]) => mutator) + + define(state: DefineState, definition: Definition, observer = observe): void { + const { id, body, inputs = [], outputs = [], output, autodisplay, autoview, automutable } = definition; + const variables = state.variables; + const v = this.main.variable(observer(state, definition), { shadow: {} }); + const vid = output ?? (outputs.length ? `cell ${id}` : null); + if (inputs.includes("display") || inputs.includes("view")) { + let displayVersion = -1; // the variable._version of currently-displayed values + const vd = new (v.constructor as typeof Variable)(2, v._module); + vd.define( + inputs.filter((i) => i !== "display" && i !== "view"), + () => { + const version = v._version; // capture version on input change + return (value: unknown) => { + if (version < displayVersion) throw new Error("stale display"); + else if (state.variables[0] !== v) throw new Error("stale display"); + else if (version > displayVersion) clear(state); + displayVersion = version; + display(state, value); + return value; + }; + } ); + v._shadow.set("display", vd); + if (inputs.includes("view")) { + const vv = new (v.constructor as typeof Variable)(2, v._module, null, { shadow: {} }); + vv._shadow.set("display", vd); + vv.define(["display"], (display) => (value: unknown) => input(display(value))); + v._shadow.set("view", vv); + } + } else if (!autodisplay) { + clear(state); } - } else { - for (const o of outputs) { - variables.push(main.variable(true).define(o, [vid!], (exports) => exports[o])); + variables.push(v.define(vid, inputs, body)); + if (output != null) { + if (autoview) { + const o = this.unprefix(output, "viewof$"); + variables.push(this.main.define(o, [output], input)); + } else if (automutable) { + const o = this.unprefix(output, "mutable "); + const x = `cell ${id}`; + v.define(o, [x], ([mutable]) => mutable); // observe live value + variables.push( + this.main.define(output, inputs, body), // initial value + this.main.define(x, [output], Mutator), + this.main.define(`mutable$${o}`, [x], ([, mutator]) => mutator) + ); + } + } else { + for (const o of outputs) { + variables.push(this.main.variable(true).define(o, [vid!], (exports) => exports[o])); + } } } -} -function unprefix(name: string, prefix: string): string { - if (!name.startsWith(prefix)) throw new Error(`expected ${prefix}: ${name}`); - return name.slice(prefix.length); + protected unprefix(name: string, prefix: string): string { + if (!name.startsWith(prefix)) throw new Error(`expected ${prefix}: ${name}`); + return name.slice(prefix.length); + } } + +const defaultInstance = new NotebookInstance(); + +export const runtime = defaultInstance.runtime; +export const main = defaultInstance.main; + +main.constructor.prototype.defines = function (this: Module, name: string): boolean { + return ( + this._scope.has(name) || + this._builtins.has(name) || + this._runtime._builtin._scope.has(name) + ); +}; + +export function define(state: DefineState, definition: Definition, observer = observe): void { + defaultInstance.define(state, definition, observer); +} \ No newline at end of file diff --git a/src/runtime/index.ts b/src/runtime/index.ts index d1594ac..6b5a5fa 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -1,8 +1,3 @@ -import type {Module} from "@observablehq/runtime"; -import {Runtime} from "@observablehq/runtime"; -import {fileAttachments} from "./stdlib/fileAttachment.js"; -import {library} from "./stdlib/index.js"; - export * from "./define.js"; export * from "./display.js"; export * from "./inspect.js"; @@ -12,14 +7,3 @@ export type * from "./stdlib/databaseClient.js"; export type * from "./stdlib/fileAttachment.js"; export {DatabaseClient} from "./stdlib/databaseClient.js"; export {FileAttachment, registerFile} from "./stdlib/fileAttachment.js"; - -export const runtime = Object.assign(new Runtime({...library, __ojs_runtime: () => runtime}), {fileAttachments}); -export const main = (runtime as typeof runtime & {main: Module}).main = runtime.module(); - -main.constructor.prototype.defines = function (this: Module, name: string): boolean { - return ( - this._scope.has(name) || - this._builtins.has(name) || - this._runtime._builtin._scope.has(name) - ); -};