Skip to content

Commit 1114cd6

Browse files
committed
Allow instantiation of multiple notebooks inside a single scope
Signed-off-by: Gordon Smith <[email protected]>
1 parent 09c603e commit 1114cd6

File tree

2 files changed

+80
-69
lines changed

2 files changed

+80
-69
lines changed

src/runtime/define.ts

Lines changed: 80 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import type {Variable, VariableDefinition} from "@observablehq/runtime";
1+
import {Runtime, type Module, type Variable, type VariableDefinition} from "@observablehq/runtime";
22
import type {DisplayState} from "./display.js";
33
import {clear, display, observe} from "./display.js";
4-
import {main} from "./index.js";
4+
import {library} from "./stdlib/index.js";
55
import {input} from "./stdlib/generators/index.js";
66
import {Mutator} from "./stdlib/mutable.js";
7+
import {fileAttachments} from "./stdlib/fileAttachment.js";
78

89
export type DefineState = DisplayState & {
910
/** the runtime variables associated with this cell */
@@ -31,61 +32,87 @@ export type Definition = {
3132
assets?: Map<string, string>;
3233
};
3334

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);
62-
}
63-
} else if (!autodisplay) {
64-
clear(state);
35+
export class NotebookInstance {
36+
37+
readonly runtime = Object.assign(new Runtime({ ...library, __ojs_runtime: () => this.runtime }), { fileAttachments });
38+
readonly main = (this.runtime as typeof this.runtime & { main: Module }).main = this.runtime.module();
39+
40+
constructor() {
6541
}
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)
42+
43+
define(state: DefineState, definition: Definition, observer = observe): void {
44+
const { id, body, inputs = [], outputs = [], output, autodisplay, autoview, automutable } = definition;
45+
const variables = state.variables;
46+
const v = this.main.variable(observer(state, definition), { shadow: {} });
47+
const vid = output ?? (outputs.length ? `cell ${id}` : null);
48+
if (inputs.includes("display") || inputs.includes("view")) {
49+
let displayVersion = -1; // the variable._version of currently-displayed values
50+
const vd = new (v.constructor as typeof Variable)(2, v._module);
51+
vd.define(
52+
inputs.filter((i) => i !== "display" && i !== "view"),
53+
() => {
54+
const version = v._version; // capture version on input change
55+
return (value: unknown) => {
56+
if (version < displayVersion) throw new Error("stale display");
57+
else if (state.variables[0] !== v) throw new Error("stale display");
58+
else if (version > displayVersion) clear(state);
59+
displayVersion = version;
60+
display(state, value);
61+
return value;
62+
};
63+
}
7964
);
65+
v._shadow.set("display", vd);
66+
if (inputs.includes("view")) {
67+
const vv = new (v.constructor as typeof Variable)(2, v._module, null, { shadow: {} });
68+
vv._shadow.set("display", vd);
69+
vv.define(["display"], (display) => (value: unknown) => input(display(value)));
70+
v._shadow.set("view", vv);
71+
}
72+
} else if (!autodisplay) {
73+
clear(state);
8074
}
81-
} else {
82-
for (const o of outputs) {
83-
variables.push(main.variable(true).define(o, [vid!], (exports) => exports[o]));
75+
variables.push(v.define(vid, inputs, body));
76+
if (output != null) {
77+
if (autoview) {
78+
const o = this.unprefix(output, "viewof$");
79+
variables.push(this.main.define(o, [output], input));
80+
} else if (automutable) {
81+
const o = this.unprefix(output, "mutable ");
82+
const x = `cell ${id}`;
83+
v.define(o, [x], ([mutable]) => mutable); // observe live value
84+
variables.push(
85+
this.main.define(output, inputs, body), // initial value
86+
this.main.define(x, [output], Mutator),
87+
this.main.define(`mutable$${o}`, [x], ([, mutator]) => mutator)
88+
);
89+
}
90+
} else {
91+
for (const o of outputs) {
92+
variables.push(this.main.variable(true).define(o, [vid!], (exports) => exports[o]));
93+
}
8494
}
8595
}
86-
}
8796

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);
97+
protected unprefix(name: string, prefix: string): string {
98+
if (!name.startsWith(prefix)) throw new Error(`expected ${prefix}: ${name}`);
99+
return name.slice(prefix.length);
100+
}
91101
}
102+
103+
const defaultInstance = new NotebookInstance();
104+
105+
export const runtime = defaultInstance.runtime;
106+
export const main = defaultInstance.main;
107+
108+
main.constructor.prototype.defines = function (this: Module, name: string): boolean {
109+
return (
110+
this._scope.has(name) ||
111+
this._builtins.has(name) ||
112+
this._runtime._builtin._scope.has(name)
113+
);
114+
};
115+
116+
export function define(state: DefineState, definition: Definition, observer = observe): void {
117+
defaultInstance.define(state, definition, observer);
118+
}

src/runtime/index.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
import type {Module} from "@observablehq/runtime";
2-
import {Runtime} from "@observablehq/runtime";
3-
import {fileAttachments} from "./stdlib/fileAttachment.js";
4-
import {library} from "./stdlib/index.js";
5-
61
export * from "./define.js";
72
export * from "./display.js";
83
export * from "./inspect.js";
@@ -12,14 +7,3 @@ export type * from "./stdlib/databaseClient.js";
127
export type * from "./stdlib/fileAttachment.js";
138
export {DatabaseClient} from "./stdlib/databaseClient.js";
149
export {FileAttachment, registerFile} from "./stdlib/fileAttachment.js";
15-
16-
export const runtime = Object.assign(new Runtime({...library, __ojs_runtime: () => runtime}), {fileAttachments});
17-
export const main = (runtime as typeof runtime & {main: Module}).main = runtime.module();
18-
19-
main.constructor.prototype.defines = function (this: Module, name: string): boolean {
20-
return (
21-
this._scope.has(name) ||
22-
this._builtins.has(name) ||
23-
this._runtime._builtin._scope.has(name)
24-
);
25-
};

0 commit comments

Comments
 (0)