@observablehq/notebook-kit 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/package.json +12 -12
  2. package/dist/src/databases/bigquery.d.ts +2 -1
  3. package/dist/src/databases/bigquery.js +6 -2
  4. package/dist/src/databases/snowflake.js +1 -1
  5. package/dist/src/javascript/imports/jsr.d.ts +1 -1
  6. package/dist/src/javascript/imports/jsr.js +1 -1
  7. package/dist/src/javascript/imports.d.ts +2 -2
  8. package/dist/src/javascript/imports.js +2 -2
  9. package/dist/src/lib/notebook.d.ts +6 -2
  10. package/dist/src/lib/serialize.js +12 -2
  11. package/dist/src/runtime/index.d.ts +2 -0
  12. package/dist/src/runtime/stdlib/dom/canvas.d.ts +1 -0
  13. package/dist/src/runtime/stdlib/dom/canvas.js +1 -0
  14. package/dist/src/runtime/stdlib/dom/download.d.ts +2 -0
  15. package/dist/src/runtime/stdlib/dom/download.js +32 -0
  16. package/dist/src/runtime/stdlib/dom/element.d.ts +2 -0
  17. package/dist/src/runtime/stdlib/dom/element.js +34 -0
  18. package/dist/src/runtime/stdlib/dom/index.d.ts +5 -0
  19. package/dist/src/runtime/stdlib/dom/index.js +5 -0
  20. package/dist/src/runtime/stdlib/dom/input.d.ts +1 -0
  21. package/dist/src/runtime/stdlib/dom/input.js +6 -0
  22. package/dist/src/runtime/stdlib/dom/range.d.ts +2 -0
  23. package/dist/src/runtime/stdlib/dom/range.js +13 -0
  24. package/dist/src/runtime/stdlib/dom/select.d.ts +2 -0
  25. package/dist/src/runtime/stdlib/dom/select.js +10 -0
  26. package/dist/src/runtime/stdlib/dom/svg.d.ts +1 -0
  27. package/dist/src/runtime/stdlib/dom/svg.js +1 -0
  28. package/dist/src/runtime/stdlib/dom/text.d.ts +1 -0
  29. package/dist/src/runtime/stdlib/dom/text.js +1 -0
  30. package/dist/src/runtime/stdlib/fileAttachment.d.ts +1 -0
  31. package/dist/src/runtime/stdlib/fileAttachment.js +11 -3
  32. package/dist/src/runtime/stdlib/generators/dark.d.ts +1 -0
  33. package/dist/src/runtime/stdlib/generators/dark.js +25 -0
  34. package/dist/src/runtime/stdlib/generators/index.d.ts +1 -0
  35. package/dist/src/runtime/stdlib/generators/index.js +1 -0
  36. package/dist/src/runtime/stdlib/index.d.ts +3 -0
  37. package/dist/src/runtime/stdlib/index.js +3 -0
  38. package/dist/src/runtime/stdlib/md.js +7 -9
  39. package/dist/src/runtime/stdlib/promises/delay.d.ts +4 -0
  40. package/dist/src/runtime/stdlib/promises/delay.js +3 -0
  41. package/dist/src/runtime/stdlib/promises/index.d.ts +3 -0
  42. package/dist/src/runtime/stdlib/promises/index.js +3 -0
  43. package/dist/src/runtime/stdlib/promises/tick.d.ts +2 -0
  44. package/dist/src/runtime/stdlib/promises/tick.js +5 -0
  45. package/dist/src/runtime/stdlib/promises/when.d.ts +2 -0
  46. package/dist/src/runtime/stdlib/promises/when.js +18 -0
  47. package/dist/src/runtime/stdlib/require.js +10 -2
  48. package/dist/src/styles/global.css +3 -7
  49. package/dist/src/templates/default.html +6 -18
  50. package/dist/src/vite/observable.js +19 -5
  51. package/package.json +12 -12
package/dist/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/observablehq/notebook-kit.git"
7
7
  },
8
- "version": "1.5.2",
8
+ "version": "1.6.0",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "test": "vitest",
@@ -42,9 +42,9 @@
42
42
  "./*.css": "./dist/src/styles/*.css"
43
43
  },
44
44
  "dependencies": {
45
- "@fontsource/inter": "^5.2.6",
46
- "@fontsource/source-serif-4": "^5.2.8",
47
- "@fontsource/spline-sans-mono": "^5.2.6",
45
+ "@fontsource-variable/inter": "^5.2.8",
46
+ "@fontsource-variable/source-serif-4": "^5.2.9",
47
+ "@fontsource-variable/spline-sans-mono": "^5.2.8",
48
48
  "@lezer/common": "^1.2.3",
49
49
  "@lezer/css": "^1.2.1",
50
50
  "@lezer/highlight": "^1.2.1",
@@ -65,28 +65,28 @@
65
65
  "vite": "^7.0.0"
66
66
  },
67
67
  "devDependencies": {
68
- "@databricks/sql": "^1.11.0",
68
+ "@databricks/sql": "^1.14.0",
69
69
  "@duckdb/node-api": "^1.3.2-alpha.26",
70
70
  "@eslint/js": "^9.29.0",
71
- "@google-cloud/bigquery": "^8.1.1",
71
+ "@google-cloud/bigquery": "^8.3.0",
72
72
  "@types/jsdom": "^21.1.7",
73
73
  "@types/markdown-it": "^14.1.2",
74
74
  "bun-types": "^1.2.20",
75
75
  "eslint": "^9.29.0",
76
76
  "globals": "^16.2.0",
77
77
  "htl": "^0.3.1",
78
- "postgres": "^3.4.7",
79
- "snowflake-sdk": "^2.1.3",
78
+ "postgres": "^3.4.9",
79
+ "snowflake-sdk": "^2.4.0",
80
80
  "tsx": "^4.20.3",
81
81
  "typescript-eslint": "^8.35.0",
82
82
  "vitest": "^3.2.4"
83
83
  },
84
84
  "peerDependencies": {
85
- "@databricks/sql": "^1.11.0",
85
+ "@databricks/sql": "^1.14.0",
86
86
  "@duckdb/node-api": "^1.3.2-alpha.26",
87
- "@google-cloud/bigquery": "^8.1.1",
88
- "postgres": "^3.4.7",
89
- "snowflake-sdk": "^2.1.3"
87
+ "@google-cloud/bigquery": "^8.3.0",
88
+ "postgres": "^3.4.9",
89
+ "snowflake-sdk": "^2.4.0"
90
90
  },
91
91
  "peerDependenciesMeta": {
92
92
  "@databricks/sql": {
@@ -5,8 +5,9 @@ export type BigQueryConfig = {
5
5
  keyFilename?: string;
6
6
  keyFile?: string;
7
7
  projectId?: string;
8
+ dataset?: string;
8
9
  };
9
- export default function bigquery({ type, ...options }: BigQueryConfig): QueryTemplateFunction;
10
+ export default function bigquery({ type, dataset, ...options }: BigQueryConfig): QueryTemplateFunction;
10
11
  export declare function replacer(this: {
11
12
  [key: string]: unknown;
12
13
  }, key: string, value: unknown): unknown;
@@ -1,11 +1,15 @@
1
1
  import { BigQuery } from "@google-cloud/bigquery";
2
2
  import { BigQueryDate, BigQueryDatetime, BigQueryTimestamp } from "@google-cloud/bigquery";
3
3
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
- export default function bigquery({ type, ...options }) {
4
+ export default function bigquery({ type, dataset, ...options }) {
5
5
  return async (strings, ...params) => {
6
6
  const bigquery = new BigQuery(options);
7
7
  const date = new Date();
8
- const [job] = await bigquery.createQueryJob({ query: strings.join("?"), params });
8
+ const [job] = await bigquery.createQueryJob({
9
+ query: strings.join("?"),
10
+ params,
11
+ ...(dataset && { defaultDataset: { datasetId: dataset } }),
12
+ });
9
13
  const [rows, , response] = await job.getQueryResults();
10
14
  return { rows, schema: getTableSchema(response.schema), duration: Date.now() - +date, date };
11
15
  };
@@ -62,7 +62,7 @@ async function execute(connection, sql, params) {
62
62
  });
63
63
  }
64
64
  function getStatementSchema(statement) {
65
- return statement.getColumns().map(getColumnSchema);
65
+ return statement.getColumns()?.map(getColumnSchema) ?? [];
66
66
  }
67
67
  function getColumnSchema(column) {
68
68
  return { name: column.getName(), type: getColumnType(column), nullable: column.isNullable() };
@@ -1,2 +1,2 @@
1
- /** If specifier is an jsr: protocol import, resolves it. */
1
+ /** If specifier is a jsr: protocol import, resolves it. */
2
2
  export declare function resolveJsrImport(specifier: string): string;
@@ -1,4 +1,4 @@
1
- /** If specifier is an jsr: protocol import, resolves it. */
1
+ /** If specifier is a jsr: protocol import, resolves it. */
2
2
  export function resolveJsrImport(specifier) {
3
3
  if (!specifier.startsWith("jsr:"))
4
4
  return specifier;
@@ -8,9 +8,9 @@ export declare function checkExports(body: Node, { input }: {
8
8
  }): void;
9
9
  /** Returns true if the body includes an import declaration. */
10
10
  export declare function hasImportDeclaration(body: Node): boolean;
11
- /** Returns true if the given node is a import.meta.resolve(…) call. */
11
+ /** Returns true if the given node is an import.meta.resolve(…) call. */
12
12
  export declare function isImportMetaResolve(node: CallExpression): boolean;
13
- /** Returns true if the given node is a import.meta.url expression. */
13
+ /** Returns true if the given node is an import.meta.url expression. */
14
14
  export declare function isImportMetaUrl(node: MemberExpression): boolean;
15
15
  export type RewriteImportOptions = {
16
16
  /** If true, resolve local imports relative to document.baseURI. */
@@ -25,7 +25,7 @@ export function hasImportDeclaration(body) {
25
25
  });
26
26
  return has;
27
27
  }
28
- /** Returns true if the given node is a import.meta.resolve(…) call. */
28
+ /** Returns true if the given node is an import.meta.resolve(…) call. */
29
29
  export function isImportMetaResolve(node) {
30
30
  return (node.callee.type === "MemberExpression" &&
31
31
  node.callee.object.type === "MetaProperty" &&
@@ -35,7 +35,7 @@ export function isImportMetaResolve(node) {
35
35
  node.callee.property.name === "resolve" &&
36
36
  node.arguments.length > 0);
37
37
  }
38
- /** Returns true if the given node is a import.meta.url expression. */
38
+ /** Returns true if the given node is an import.meta.url expression. */
39
39
  export function isImportMetaUrl(node) {
40
40
  return (node.object.type === "MetaProperty" &&
41
41
  node.object.meta.name === "import" &&
@@ -1,11 +1,15 @@
1
1
  export type NotebookTheme = "air" | "coffee" | "cotton" | "deep-space" | "glacier" | "ink" | "midnight" | "near-midnight" | "ocean-floor" | "parchment" | "slate" | "stark" | "sun-faded";
2
+ export type LightDarkNotebookTheme = {
3
+ light: NotebookTheme;
4
+ dark: NotebookTheme;
5
+ };
2
6
  export interface NotebookSpec {
3
7
  /** the notebook’s cells, in top-to-bottom document order */
4
8
  cells?: CellSpec[];
5
9
  /** the notebook title, if any; extracted from the first h1 */
6
10
  title?: string;
7
- /** the notebook theme; defaults to "air" */
8
- theme?: NotebookTheme;
11
+ /** the notebook theme(s); defaults to "air" */
12
+ theme?: NotebookTheme | LightDarkNotebookTheme;
9
13
  /** if true, don’t allow editing */
10
14
  readOnly?: boolean;
11
15
  }
@@ -1,8 +1,9 @@
1
1
  import { toNotebook } from "./notebook.js";
2
2
  import { isEmpty } from "./text.js";
3
+ const DEFAULT_THEME = "air";
3
4
  export function serialize(notebook, { document = globalThis.document } = {}) {
4
5
  const _notebook = document.createElement("notebook");
5
- _notebook.setAttribute("theme", notebook.theme);
6
+ _notebook.setAttribute("theme", serializeTheme(notebook.theme));
6
7
  if (notebook.readOnly)
7
8
  _notebook.setAttribute("readonly", "");
8
9
  _notebook.appendChild(document.createTextNode("\n "));
@@ -126,8 +127,17 @@ function deserializeFormat(format) {
126
127
  return format;
127
128
  }
128
129
  }
130
+ function serializeTheme(theme) {
131
+ return typeof theme === "string" ? theme : `light-dark(${theme.light}, ${theme.dark})`;
132
+ }
129
133
  function deserializeTheme(theme) {
130
- return theme ?? "air";
134
+ theme = theme?.trim().toLowerCase() ?? DEFAULT_THEME;
135
+ const match = /^light-dark\(([\w-]+),\s*([\w-]+)\)$/.exec(theme);
136
+ if (match) {
137
+ const [, light, dark] = match;
138
+ return { light, dark };
139
+ }
140
+ return theme;
131
141
  }
132
142
  function dedent(text) {
133
143
  const lines = text.split(/\r\n?|\n/);
@@ -56,6 +56,7 @@ export declare class NotebookRuntime {
56
56
  }>;
57
57
  topojson: () => Promise<any>;
58
58
  vl: () => Promise<any>;
59
+ dark: () => ObservableAsyncGenerator<boolean>;
59
60
  now: () => AsyncGenerator<number, void, unknown>;
60
61
  width: () => ObservableAsyncGenerator<number>;
61
62
  DatabaseClient: () => {
@@ -108,6 +109,7 @@ export declare class NotebookRuntime {
108
109
  };
109
110
  };
110
111
  Mutable: () => typeof import("./stdlib/mutable.js").Mutable;
112
+ Promises: () => typeof import("./stdlib/promises/index.js");
111
113
  DOM: () => typeof import("./stdlib/dom/index.js");
112
114
  require: () => typeof import("./stdlib/require.js").require;
113
115
  __ojs_observer: () => () => import("./stdlib/observer.js").Observer;
@@ -1 +1,2 @@
1
+ /** @deprecated */
1
2
  export declare function canvas(width: number, height: number): HTMLCanvasElement;
@@ -1,3 +1,4 @@
1
+ /** @deprecated */
1
2
  export function canvas(width, height) {
2
3
  const canvas = document.createElement("canvas");
3
4
  canvas.width = width;
@@ -0,0 +1,2 @@
1
+ /** @deprecated */
2
+ export declare function download(value: unknown, name?: string, label?: string): HTMLAnchorElement;
@@ -0,0 +1,32 @@
1
+ /** @deprecated */
2
+ export function download(value, name = "untitled", label = "Save") {
3
+ const a = document.createElement("a");
4
+ const b = a.appendChild(document.createElement("button"));
5
+ b.textContent = label;
6
+ a.download = name;
7
+ async function reset() {
8
+ await new Promise(requestAnimationFrame);
9
+ URL.revokeObjectURL(a.href);
10
+ a.removeAttribute("href");
11
+ b.textContent = label;
12
+ b.disabled = false;
13
+ }
14
+ a.onclick = async (event) => {
15
+ b.disabled = true;
16
+ if (a.href)
17
+ return reset(); // Already saved.
18
+ b.textContent = "Saving…";
19
+ try {
20
+ const object = await (typeof value === "function" ? value() : value);
21
+ b.textContent = "Download";
22
+ a.href = URL.createObjectURL(object);
23
+ }
24
+ catch {
25
+ b.textContent = label;
26
+ }
27
+ if (event.eventPhase)
28
+ return reset(); // Already downloaded.
29
+ b.disabled = false;
30
+ };
31
+ return a;
32
+ }
@@ -0,0 +1,2 @@
1
+ /** @deprecated */
2
+ export declare function element(name: string, attributes: Record<string, string>): Element;
@@ -0,0 +1,34 @@
1
+ const namespaces = {
2
+ math: "http://www.w3.org/1998/Math/MathML",
3
+ svg: "http://www.w3.org/2000/svg",
4
+ xhtml: "http://www.w3.org/1999/xhtml",
5
+ xlink: "http://www.w3.org/1999/xlink",
6
+ xml: "http://www.w3.org/XML/1998/namespace",
7
+ xmlns: "http://www.w3.org/2000/xmlns/"
8
+ };
9
+ function isNamespace(prefix) {
10
+ return Object.prototype.hasOwnProperty.call(namespaces, prefix);
11
+ }
12
+ /** @deprecated */
13
+ export function element(name, attributes) {
14
+ let prefix = (name += "");
15
+ let i = prefix.indexOf(":");
16
+ if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns")
17
+ name = name.slice(i + 1);
18
+ const element = isNamespace(prefix)
19
+ ? document.createElementNS(namespaces[prefix], name)
20
+ : document.createElement(name);
21
+ if (attributes) {
22
+ for (let key in attributes) {
23
+ const value = attributes[key];
24
+ i = (prefix = key).indexOf(":");
25
+ if (i >= 0 && (prefix = key.slice(0, i)) !== "xmlns")
26
+ key = key.slice(i + 1);
27
+ if (isNamespace(prefix))
28
+ element.setAttributeNS(namespaces[prefix], key, value);
29
+ else
30
+ element.setAttribute(key, value);
31
+ }
32
+ }
33
+ return element;
34
+ }
@@ -1,5 +1,10 @@
1
1
  export { canvas } from "./canvas.js";
2
2
  export { context2d } from "./context2d.js";
3
+ export { download } from "./download.js";
4
+ export { element } from "./element.js";
5
+ export { input } from "./input.js";
6
+ export { range } from "./range.js";
7
+ export { select } from "./select.js";
3
8
  export { svg } from "./svg.js";
4
9
  export { text } from "./text.js";
5
10
  export { uid } from "./uid.js";
@@ -1,5 +1,10 @@
1
1
  export { canvas } from "./canvas.js";
2
2
  export { context2d } from "./context2d.js";
3
+ export { download } from "./download.js";
4
+ export { element } from "./element.js";
5
+ export { input } from "./input.js";
6
+ export { range } from "./range.js";
7
+ export { select } from "./select.js";
3
8
  export { svg } from "./svg.js";
4
9
  export { text } from "./text.js";
5
10
  export { uid } from "./uid.js";
@@ -0,0 +1 @@
1
+ export declare function input(type?: HTMLInputElement["type"]): HTMLInputElement;
@@ -0,0 +1,6 @@
1
+ export function input(type) {
2
+ const input = document.createElement("input");
3
+ if (type != null)
4
+ input.type = type;
5
+ return input;
6
+ }
@@ -0,0 +1,2 @@
1
+ /** @deprecated */
2
+ export declare function range(min?: number, max?: number, step?: number): HTMLInputElement;
@@ -0,0 +1,13 @@
1
+ /** @deprecated */
2
+ export function range(min, max, step) {
3
+ if (arguments.length === 1) {
4
+ max = min;
5
+ min = undefined;
6
+ }
7
+ const input = document.createElement("input");
8
+ input.min = String((min = min == null ? 0 : +min));
9
+ input.max = String((max = max == null ? 1 : +max));
10
+ input.step = step == null ? "any" : String((step = +step));
11
+ input.type = "range";
12
+ return input;
13
+ }
@@ -0,0 +1,2 @@
1
+ /** @deprecated */
2
+ export declare function select(values: string[]): HTMLSelectElement;
@@ -0,0 +1,10 @@
1
+ /** @deprecated */
2
+ export function select(values) {
3
+ const select = document.createElement("select");
4
+ for (const value of values) {
5
+ const option = document.createElement("option");
6
+ option.value = option.textContent = value;
7
+ select.appendChild(option);
8
+ }
9
+ return select;
10
+ }
@@ -1 +1,2 @@
1
+ /** @deprecated */
1
2
  export declare function svg(width: number, height: number): SVGSVGElement;
@@ -1,3 +1,4 @@
1
+ /** @deprecated */
1
2
  export function svg(width, height) {
2
3
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
3
4
  svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
@@ -1 +1,2 @@
1
+ /** @deprecated */
1
2
  export declare function text(value: string): Text;
@@ -1,3 +1,4 @@
1
+ /** @deprecated */
1
2
  export function text(value) {
2
3
  return document.createTextNode(value);
3
4
  }
@@ -58,6 +58,7 @@ export interface FileInfo {
58
58
  lastModified?: number;
59
59
  size?: number;
60
60
  }
61
+ export declare function requireFileRegistration(value: boolean): void;
61
62
  export declare function registerFile(name: string, info: FileInfo | null, base?: string | URL): FileAttachmentImpl | undefined;
62
63
  export declare abstract class AbstractFile implements FileAttachment {
63
64
  name: string;
@@ -1,15 +1,23 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  const files = new Map();
3
- // TODO Enforce that files have been registered; throw error if not found.
3
+ let strict = false;
4
4
  export const FileAttachment = (name, base = document.baseURI) => {
5
5
  const href = new URL(name, base).href;
6
6
  let file = files.get(href);
7
7
  if (!file) {
8
- file = new FileAttachmentImpl(href, name.split("/").pop());
9
- files.set(href, file);
8
+ if (strict) {
9
+ throw new Error(`File not found: ${name}`);
10
+ }
11
+ else {
12
+ file = new FileAttachmentImpl(href, name.split("/").pop());
13
+ files.set(href, file);
14
+ }
10
15
  }
11
16
  return file;
12
17
  };
18
+ export function requireFileRegistration(value) {
19
+ strict = value;
20
+ }
13
21
  export function registerFile(name, info, base = location.href) {
14
22
  const href = new URL(name, base).href;
15
23
  if (info == null) {
@@ -0,0 +1 @@
1
+ export declare function dark(): ObservableAsyncGenerator<boolean>;
@@ -0,0 +1,25 @@
1
+ import { observe } from "./observe.js";
2
+ export function dark() {
3
+ return observe((notify) => {
4
+ let dark;
5
+ const media = matchMedia("(prefers-color-scheme: dark)");
6
+ const probe = document.createElement("div");
7
+ probe.style.transitionProperty = "color, background-color";
8
+ probe.style.transitionDuration = "1ms";
9
+ const changed = () => {
10
+ const s = getComputedStyle(document.body).getPropertyValue("color-scheme").split(/\s+/);
11
+ const d = s.includes("light") && s.includes("dark") ? media.matches : s.includes("dark");
12
+ if (dark === d)
13
+ return;
14
+ notify((dark = d));
15
+ };
16
+ document.body.appendChild(probe);
17
+ changed();
18
+ probe.addEventListener("transitionstart", changed);
19
+ media.addEventListener("change", changed);
20
+ return () => {
21
+ probe.remove();
22
+ media.removeEventListener("change", changed);
23
+ };
24
+ });
25
+ }
@@ -1,3 +1,4 @@
1
+ export { dark } from "./dark.js";
1
2
  export { input } from "./input.js";
2
3
  export { now } from "./now.js";
3
4
  export { observe } from "./observe.js";
@@ -1,3 +1,4 @@
1
+ export { dark } from "./dark.js";
1
2
  export { input } from "./input.js";
2
3
  export { now } from "./now.js";
3
4
  export { observe } from "./observe.js";
@@ -5,6 +5,7 @@ import * as Generators from "./generators/index.js";
5
5
  import { Interpreter } from "./interpreter.js";
6
6
  import { Mutable } from "./mutable.js";
7
7
  import { Observer } from "./observer.js";
8
+ import * as Promises from "./promises/index.js";
8
9
  import { require } from "./require.js";
9
10
  export declare const root: HTMLElement;
10
11
  export declare const library: {
@@ -45,6 +46,7 @@ export declare const library: {
45
46
  }>;
46
47
  topojson: () => Promise<any>;
47
48
  vl: () => Promise<any>;
49
+ dark: () => ObservableAsyncGenerator<boolean>;
48
50
  now: () => AsyncGenerator<number, void, unknown>;
49
51
  width: () => ObservableAsyncGenerator<number>;
50
52
  DatabaseClient: () => {
@@ -97,6 +99,7 @@ export declare const library: {
97
99
  };
98
100
  };
99
101
  Mutable: () => typeof Mutable;
102
+ Promises: () => typeof Promises;
100
103
  DOM: () => typeof DOM;
101
104
  require: () => typeof require;
102
105
  __ojs_observer: () => () => Observer;
@@ -5,11 +5,13 @@ import * as Generators from "./generators/index.js";
5
5
  import { Interpreter } from "./interpreter.js";
6
6
  import { Mutable } from "./mutable.js";
7
7
  import { Observer } from "./observer.js";
8
+ import * as Promises from "./promises/index.js";
8
9
  import * as recommendedLibraries from "./recommendedLibraries.js";
9
10
  import { require } from "./require.js";
10
11
  import * as sampleDatasets from "./sampleDatasets.js";
11
12
  export const root = document.querySelector("main") ?? document.body;
12
13
  export const library = {
14
+ dark: () => Generators.dark(),
13
15
  now: () => Generators.now(),
14
16
  width: () => Generators.width(root),
15
17
  DatabaseClient: () => DatabaseClient,
@@ -17,6 +19,7 @@ export const library = {
17
19
  Generators: () => Generators,
18
20
  Interpreter: () => Interpreter,
19
21
  Mutable: () => Mutable,
22
+ Promises: () => Promises, // deprecated!
20
23
  DOM: () => DOM, // deprecated!
21
24
  require: () => require, // deprecated!
22
25
  __ojs_observer: () => () => new Observer(),
@@ -13,19 +13,19 @@ export function MarkdownRenderer({ document = window.document } = {}) {
13
13
  let fragment = null;
14
14
  let partIndex = -1;
15
15
  const parts = [];
16
- // Concatenate the text using comments as placeholders.
16
+ // Concatenate the text using anchors as placeholders.
17
17
  for (let i = 0, n = values.length; i < n; ++i) {
18
18
  const value = values[i];
19
19
  if (value instanceof Node) {
20
20
  parts[++partIndex] = value;
21
- source += `<!--o:${partIndex}-->`;
21
+ source += `<a id=o:${partIndex}></a>`;
22
22
  }
23
23
  else if (Array.isArray(value)) {
24
24
  for (const node of value) {
25
25
  if (node instanceof Node) {
26
26
  if (fragment === null) {
27
27
  parts[++partIndex] = fragment = document.createDocumentFragment();
28
- source += `<!--o:${partIndex}-->`;
28
+ source += `<a id=o:${partIndex}></a>`;
29
29
  }
30
30
  fragment.appendChild(node);
31
31
  }
@@ -44,14 +44,12 @@ export function MarkdownRenderer({ document = window.document } = {}) {
44
44
  // Render the text.
45
45
  const root = document.createElement("div");
46
46
  root.innerHTML = mi.render(source);
47
- // Walk the rendered content to replace comment placeholders.
47
+ // Walk the rendered content to replace anchor placeholders.
48
48
  if (++partIndex > 0) {
49
49
  const nodes = new Array(partIndex);
50
- const walker = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT, null);
51
- while (walker.nextNode()) {
52
- const node = walker.currentNode;
53
- if (/^o:\d+$/.test(node.nodeValue)) {
54
- nodes[+node.nodeValue.slice(2)] = node;
50
+ for (const node of root.querySelectorAll("a[id^='o:']")) {
51
+ if (/^o:\d+$/.test(node.id)) {
52
+ nodes[+node.id.slice(2)] = node;
55
53
  }
56
54
  }
57
55
  for (let i = 0; i < partIndex; ++i) {
@@ -0,0 +1,4 @@
1
+ /** @deprecated */
2
+ export declare function delay(duration: number): Promise<void>;
3
+ /** @deprecated */
4
+ export declare function delay<T>(duration: number, value: T): Promise<T>;
@@ -0,0 +1,3 @@
1
+ export function delay(duration, value) {
2
+ return new Promise((resolve) => setTimeout(() => resolve(value), duration));
3
+ }
@@ -0,0 +1,3 @@
1
+ export { delay } from "./delay.js";
2
+ export { tick } from "./tick.js";
3
+ export { when } from "./when.js";
@@ -0,0 +1,3 @@
1
+ export { delay } from "./delay.js";
2
+ export { tick } from "./tick.js";
3
+ export { when } from "./when.js";
@@ -0,0 +1,2 @@
1
+ /** @deprecated */
2
+ export declare function tick<T>(duration: number, value: T): Promise<T>;
@@ -0,0 +1,5 @@
1
+ import { when } from "./when.js";
2
+ /** @deprecated */
3
+ export function tick(duration, value) {
4
+ return when(Math.ceil((Date.now() + 1) / duration) * duration, value);
5
+ }
@@ -0,0 +1,2 @@
1
+ /** @deprecated */
2
+ export declare function when<T>(time: number, value: T): Promise<T>;
@@ -0,0 +1,18 @@
1
+ const timeouts = new Map();
2
+ /** @deprecated */
3
+ export async function when(time, value) {
4
+ let timeout = timeouts.get((time = +time));
5
+ if (!timeout) {
6
+ const now = Date.now();
7
+ const delay = time - now;
8
+ if (delay > 0) {
9
+ if (delay > 0x7fffffff)
10
+ throw new Error("too long to wait");
11
+ timeout = new Promise((resolve) => setTimeout(resolve, delay));
12
+ timeout.then(() => timeouts.delete(time));
13
+ timeouts.set(time, timeout);
14
+ }
15
+ }
16
+ await timeout;
17
+ return value;
18
+ }
@@ -1,8 +1,9 @@
1
1
  require.resolve = resolve;
2
+ const cache = new WeakMap();
2
3
  export function require(...specifiers) {
3
4
  return specifiers.length === 1
4
- ? import(/* @vite-ignore */ resolve(specifiers[0]))
5
- : Promise.all(specifiers.map((s) => require(s))).then((modules) => Object.assign({}, ...modules));
5
+ ? import(/* @vite-ignore */ resolve(specifiers[0])).then(objectify)
6
+ : Promise.all(specifiers.map((s) => require(s))).then((modules) => Object.assign({}, ...modules)); // prettier-ignore
6
7
  }
7
8
  function parseNpmSpecifier(specifier) {
8
9
  const parts = specifier.split("/");
@@ -22,6 +23,13 @@ function resolve(_specifier) {
22
23
  const { name, range, path } = parseNpmSpecifier(specifier);
23
24
  return `https://cdn.jsdelivr.net/npm/${name}${range}${path + (isFile(path) || isDirectory(path) ? "" : "/+esm")}`;
24
25
  }
26
+ /** Allow mutation of required modules while maintaining a canonical instance; ugly! */
27
+ function objectify(module) {
28
+ let object = cache.get(module);
29
+ if (!object)
30
+ cache.set(module, (object = { ...module }));
31
+ return object;
32
+ }
25
33
  /** Returns true for e.g. https://example.com/ */
26
34
  function isProtocol(specifier) {
27
35
  return /^\w+:/.test(specifier);
@@ -1,11 +1,7 @@
1
1
  :root {
2
- --serif:
3
- "Source Serif 4", "Iowan Old Style", "Apple Garamond", "Palatino Linotype", "Times New Roman",
4
- "Droid Serif", Times, serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
5
- --sans-serif:
6
- "Inter", -apple-system, BlinkMacSystemFont, "avenir next", avenir, helvetica, "helvetica neue",
7
- ubuntu, roboto, noto, "segoe ui", arial, sans-serif;
8
- --monospace: "Spline Sans Mono", Menlo, Consolas, monospace;
2
+ --serif: "Source Serif 4 Variable", ui-serif, serif;
3
+ --sans-serif: "Inter Variable", ui-sans-serif, sans-serif;
4
+ --monospace: "Spline Sans Mono Variable", Menlo, Consolas, monospace;
9
5
  --monospace-font: 14px/1.5 var(--monospace);
10
6
  --max-width: calc(1024px - 4 * 16px); /* max-w-5xl p-4 */
11
7
  }
@@ -5,24 +5,12 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <style type="text/css">
7
7
  @import url("observable:styles/index.css");
8
- @import url("@fontsource/spline-sans-mono/400.css");
9
- @import url("@fontsource/spline-sans-mono/600.css");
10
- @import url("@fontsource/spline-sans-mono/700.css");
11
- @import url("@fontsource/spline-sans-mono/400-italic.css");
12
- @import url("@fontsource/spline-sans-mono/600-italic.css");
13
- @import url("@fontsource/spline-sans-mono/700-italic.css");
14
- @import url("@fontsource/source-serif-4/400.css");
15
- @import url("@fontsource/source-serif-4/700.css");
16
- @import url("@fontsource/source-serif-4/400-italic.css");
17
- @import url("@fontsource/source-serif-4/700-italic.css");
18
- @import url("@fontsource/inter/400.css");
19
- @import url("@fontsource/inter/500.css");
20
- @import url("@fontsource/inter/600.css");
21
- @import url("@fontsource/inter/700.css");
22
- @import url("@fontsource/inter/400-italic.css");
23
- @import url("@fontsource/inter/500-italic.css");
24
- @import url("@fontsource/inter/600-italic.css");
25
- @import url("@fontsource/inter/700-italic.css");
8
+ @import url("@fontsource-variable/inter/wght.css");
9
+ @import url("@fontsource-variable/inter/wght-italic.css");
10
+ @import url("@fontsource-variable/source-serif-4/wght.css");
11
+ @import url("@fontsource-variable/source-serif-4/wght-italic.css");
12
+ @import url("@fontsource-variable/spline-sans-mono/wght.css");
13
+ @import url("@fontsource-variable/spline-sans-mono/wght-italic.css");
26
14
  </style>
27
15
  </head>
28
16
  <body>
@@ -1,5 +1,5 @@
1
1
  import { fork, spawn } from "node:child_process";
2
- import { createWriteStream, existsSync } from "node:fs";
2
+ import { createWriteStream, existsSync, unlinkSync } from "node:fs";
3
3
  import { mkdir, readFile } from "node:fs/promises";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  import { relative } from "node:path/posix";
@@ -78,7 +78,12 @@ export function observable({ window = new JSDOM().window, parser = new window.DO
78
78
  const child = fork(fileURLToPath(import.meta.resolve("../../bin/query.js")), args);
79
79
  await new Promise((resolve, reject) => {
80
80
  child.on("error", reject);
81
- child.on("exit", resolve); // TODO check exit code
81
+ child.on("exit", (code) => {
82
+ if (code === 0)
83
+ return resolve();
84
+ unlinkSync(cachePath); // don’t pollute cache with failure
85
+ reject(new Error(`${cell.database} query exited with ${code}`));
86
+ });
82
87
  });
83
88
  }
84
89
  cell.mode = "js";
@@ -98,7 +103,12 @@ export function observable({ window = new JSDOM().window, parser = new window.DO
98
103
  child.stdout.pipe(createWriteStream(cachePath));
99
104
  await new Promise((resolve, reject) => {
100
105
  child.on("error", reject);
101
- child.on("exit", resolve); // TODO check exit code
106
+ child.on("exit", (code) => {
107
+ if (code === 0)
108
+ return resolve();
109
+ unlinkSync(cachePath); // don’t pollute cache with failure
110
+ reject(new Error(`${mode} interpreter exited with ${code}`));
111
+ });
102
112
  });
103
113
  }
104
114
  if (format === "html" && !hidden) {
@@ -127,8 +137,12 @@ export function observable({ window = new JSDOM().window, parser = new window.DO
127
137
  throw new Error("body not found");
128
138
  return (`<!doctype html>` +
129
139
  output.slice(0, i) +
130
- `<style type="text/css">
131
- @import url("observable:styles/theme-${notebook.theme}.css");
140
+ `<style type="text/css">${typeof notebook.theme === "string"
141
+ ? `
142
+ @import url("observable:styles/theme-${notebook.theme}.css");`
143
+ : `
144
+ @import url("observable:styles/theme-${notebook.theme.light}.css") (prefers-color-scheme: light);
145
+ @import url("observable:styles/theme-${notebook.theme.dark}.css") (prefers-color-scheme: dark);`}
132
146
  </style><script type="module">
133
147
  import {define} from "observable:runtime";${Array.from(assets)
134
148
  .map((asset, i) => `
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/observablehq/notebook-kit.git"
7
7
  },
8
- "version": "1.5.2",
8
+ "version": "1.6.0",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "test": "vitest",
@@ -42,9 +42,9 @@
42
42
  "./*.css": "./dist/src/styles/*.css"
43
43
  },
44
44
  "dependencies": {
45
- "@fontsource/inter": "^5.2.6",
46
- "@fontsource/source-serif-4": "^5.2.8",
47
- "@fontsource/spline-sans-mono": "^5.2.6",
45
+ "@fontsource-variable/inter": "^5.2.8",
46
+ "@fontsource-variable/source-serif-4": "^5.2.9",
47
+ "@fontsource-variable/spline-sans-mono": "^5.2.8",
48
48
  "@lezer/common": "^1.2.3",
49
49
  "@lezer/css": "^1.2.1",
50
50
  "@lezer/highlight": "^1.2.1",
@@ -65,28 +65,28 @@
65
65
  "vite": "^7.0.0"
66
66
  },
67
67
  "devDependencies": {
68
- "@databricks/sql": "^1.11.0",
68
+ "@databricks/sql": "^1.14.0",
69
69
  "@duckdb/node-api": "^1.3.2-alpha.26",
70
70
  "@eslint/js": "^9.29.0",
71
- "@google-cloud/bigquery": "^8.1.1",
71
+ "@google-cloud/bigquery": "^8.3.0",
72
72
  "@types/jsdom": "^21.1.7",
73
73
  "@types/markdown-it": "^14.1.2",
74
74
  "bun-types": "^1.2.20",
75
75
  "eslint": "^9.29.0",
76
76
  "globals": "^16.2.0",
77
77
  "htl": "^0.3.1",
78
- "postgres": "^3.4.7",
79
- "snowflake-sdk": "^2.1.3",
78
+ "postgres": "^3.4.9",
79
+ "snowflake-sdk": "^2.4.0",
80
80
  "tsx": "^4.20.3",
81
81
  "typescript-eslint": "^8.35.0",
82
82
  "vitest": "^3.2.4"
83
83
  },
84
84
  "peerDependencies": {
85
- "@databricks/sql": "^1.11.0",
85
+ "@databricks/sql": "^1.14.0",
86
86
  "@duckdb/node-api": "^1.3.2-alpha.26",
87
- "@google-cloud/bigquery": "^8.1.1",
88
- "postgres": "^3.4.7",
89
- "snowflake-sdk": "^2.1.3"
87
+ "@google-cloud/bigquery": "^8.3.0",
88
+ "postgres": "^3.4.9",
89
+ "snowflake-sdk": "^2.4.0"
90
90
  },
91
91
  "peerDependenciesMeta": {
92
92
  "@databricks/sql": {