Skip to content

Allow instantiation of multiple notebooks inside a single scope #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 80 additions & 53 deletions src/runtime/define.ts
Original file line number Diff line number Diff line change
@@ -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 */
Expand Down Expand Up @@ -31,61 +32,87 @@ export type Definition = {
assets?: Map<string, string>;
};

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);
}
16 changes: 0 additions & 16 deletions src/runtime/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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)
);
};