Skip to content

Commit 9e7af60

Browse files
committed
Add mutable
1 parent 7e6f846 commit 9e7af60

File tree

5 files changed

+89
-0
lines changed

5 files changed

+89
-0
lines changed

runtime/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import {group} from "d3-array";
66
import {dispatch as d3Dispatch} from "d3-dispatch";
77
import * as stdlib from "./stdlib.js";
88
import {OUTPUT_MARK} from "./constant.js";
9+
import {Mutable} from "./mutable.js";
910

1011
const PREFIX = `//${OUTPUT_MARK}`;
1112

1213
const BUILTINS = {
1314
recho: () => stdlib,
15+
Mutable: () => Mutable,
1416
};
1517

1618
function uid() {

runtime/mutable.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// https://github.com/observablehq/notebook-kit/blob/main/src/runtime/stdlib/mutable.ts
2+
import {observe} from "./observe.js";
3+
4+
// Mutable returns a generator with a value getter/setting that allows the
5+
// generated value to be mutated. Therefore, direct mutation is only allowed
6+
// within the defining cell, but the cell can also export functions that allows
7+
// other cells to mutate the value as desired.
8+
export function Mutable(value) {
9+
let change = undefined;
10+
const mutable = observe((_) => {
11+
change = _;
12+
if (value !== undefined) change(value);
13+
});
14+
return Object.defineProperty(mutable, "value", {
15+
get: () => value,
16+
set: (x) => ((value = x), void change?.(value)),
17+
});
18+
}
19+
20+
export function Mutator(value) {
21+
const mutable = Mutable(value);
22+
return [
23+
mutable,
24+
{
25+
get value() {
26+
return mutable.value;
27+
},
28+
set value(v) {
29+
mutable.value = v;
30+
},
31+
},
32+
];
33+
}

runtime/observe.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// https://github.com/observablehq/notebook-kit/blob/main/src/runtime/stdlib/generators/observe.ts
2+
3+
export async function* observe(initialize) {
4+
let resolve = undefined;
5+
let value = undefined;
6+
let stale = false;
7+
8+
const dispose = initialize((x) => {
9+
value = x;
10+
if (resolve) {
11+
resolve(x);
12+
resolve = undefined;
13+
} else {
14+
stale = true;
15+
}
16+
return x;
17+
});
18+
19+
if (dispose != null && typeof dispose !== "function") {
20+
throw new Error(
21+
typeof dispose === "object" && "then" in dispose && typeof dispose.then === "function"
22+
? "async initializers are not supported"
23+
: "initializer returned something, but not a dispose function",
24+
);
25+
}
26+
27+
try {
28+
while (true) {
29+
yield stale ? ((stale = false), value) : new Promise((_) => (resolve = _));
30+
}
31+
} finally {
32+
if (dispose != null) {
33+
dispose();
34+
}
35+
}
36+
}

test/js/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export {mandelbrotSet} from "./mandelbrot-set.js";
1414
export {matrixRain} from "./matrix-rain.js";
1515
export {jsDocString} from "./js-doc-string.js";
1616
export {commentLink} from "./comment-link.js";
17+
export {mutable} from "./mutable.js";

test/js/mutable.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export const mutable = `const {a, getA, setA} = (() => {
2+
const a = Mutable(0);
3+
return {a, getA: () => a.value, setA: (value) => a.value = value};
4+
})();
5+
6+
setA(a + 1);
7+
8+
echo(a);`;
9+
10+
// export const mutable = `const {a, getA, setA} = (() => {
11+
// const a = Mutable(0);
12+
// return {a, getA: () => a.value, setA: (value) => a.value = value};
13+
// })();
14+
15+
// setA(getA() + 1);
16+
17+
// echo(a);`;

0 commit comments

Comments
 (0)