@observablehq/notebook-kit 1.0.1 → 1.1.0-rc.10

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 (60) hide show
  1. package/dist/bin/build.js +7 -0
  2. package/dist/bin/download.js +15 -19
  3. package/dist/package.json +31 -2
  4. package/dist/src/databases/duckdb.d.ts +2 -0
  5. package/dist/src/databases/duckdb.js +72 -0
  6. package/dist/src/databases/index.d.ts +46 -0
  7. package/dist/src/databases/index.js +54 -0
  8. package/dist/src/databases/options.d.ts +3 -0
  9. package/dist/src/databases/options.js +6 -0
  10. package/dist/src/databases/postgres.d.ts +2 -0
  11. package/dist/src/databases/postgres.js +95 -0
  12. package/dist/src/databases/snowflake.d.ts +2 -0
  13. package/dist/src/databases/snowflake.js +102 -0
  14. package/dist/src/databases/sqlite.d.ts +2 -0
  15. package/dist/src/databases/sqlite.js +64 -0
  16. package/dist/src/javascript/imports/npm.js +2 -0
  17. package/dist/src/javascript/observable.js +24 -3
  18. package/dist/src/javascript/template.d.ts +3 -0
  19. package/dist/src/javascript/template.js +17 -1
  20. package/dist/src/javascript/transpile.d.ts +4 -2
  21. package/dist/src/javascript/transpile.js +30 -11
  22. package/dist/src/javascript/transpile.test.js +16 -0
  23. package/dist/src/lib/error.d.ts +6 -0
  24. package/dist/src/lib/error.js +6 -0
  25. package/dist/src/lib/hash.d.ts +2 -0
  26. package/dist/src/lib/hash.js +20 -0
  27. package/dist/src/lib/hash.test.d.ts +1 -0
  28. package/dist/src/lib/hash.test.js +28 -0
  29. package/dist/src/lib/notebook.d.ts +11 -1
  30. package/dist/src/lib/notebook.js +10 -3
  31. package/dist/src/lib/notebook.test.js +10 -2
  32. package/dist/src/lib/serialize.d.ts +3 -1
  33. package/dist/src/lib/serialize.js +13 -4
  34. package/dist/src/lib/serialize.test.js +10 -3
  35. package/dist/src/lib/sluggify.d.ts +6 -0
  36. package/dist/src/lib/sluggify.js +22 -0
  37. package/dist/src/lib/sluggify.test.d.ts +1 -0
  38. package/dist/src/lib/sluggify.test.js +51 -0
  39. package/dist/src/runtime/define.d.ts +2 -2
  40. package/dist/src/runtime/define.js +2 -2
  41. package/dist/src/runtime/display.d.ts +2 -0
  42. package/dist/src/runtime/display.js +5 -1
  43. package/dist/src/runtime/index.d.ts +100 -1
  44. package/dist/src/runtime/index.js +29 -3
  45. package/dist/src/runtime/stdlib/databaseClient.d.ts +45 -0
  46. package/dist/src/runtime/stdlib/databaseClient.js +77 -0
  47. package/dist/src/runtime/stdlib/duckdb.js +7 -4
  48. package/dist/src/runtime/stdlib/fileAttachment.d.ts +38 -11
  49. package/dist/src/runtime/stdlib/fileAttachment.js +21 -10
  50. package/dist/src/runtime/stdlib/generators/input.d.ts +1 -1
  51. package/dist/src/runtime/stdlib/index.d.ts +43 -6
  52. package/dist/src/runtime/stdlib/index.js +5 -5
  53. package/dist/src/styles/global.css +1 -1
  54. package/dist/src/templates/default.html +18 -18
  55. package/dist/src/vite/config.js +5 -2
  56. package/dist/src/vite/observable.d.ts +25 -6
  57. package/dist/src/vite/observable.js +42 -15
  58. package/package.json +31 -2
  59. package/dist/src/runtime/stdlib/sql.d.ts +0 -5
  60. package/dist/src/runtime/stdlib/sql.js +0 -5
@@ -1,12 +1,31 @@
1
- import type { PluginOption } from "vite";
1
+ import type { PluginOption, IndexHtmlTransformContext } from "vite";
2
+ import type { Notebook } from "../lib/notebook.js";
3
+ /**
4
+ * A function which performs a per-page transformation of the template HTML.
5
+ *
6
+ * @param source The source of the template (typically HTML).
7
+ * @param context The Vite plugin context.
8
+ * @returns The transformed template source HTML.
9
+ */
10
+ export type TemplateTransform = (source: string, context: IndexHtmlTransformContext) => string | Promise<string>;
11
+ /**
12
+ * A function which transforms the parsed notebook.
13
+ *
14
+ * @param notebook The current (parsed) notebook.
15
+ * @param context The Vite plugin context.
16
+ * @returns The transformed notebook.
17
+ */
18
+ export type NotebookTransform = (notebook: Notebook, context: IndexHtmlTransformContext) => Notebook | Promise<Notebook>;
2
19
  export interface ObservableOptions {
3
- /** The global window, for the default parser and serializer implementations. */
4
- window?: Pick<typeof globalThis, "DOMParser" | "XMLSerializer">;
20
+ /** The global window, for the default parser implementations. */
21
+ window?: Pick<typeof globalThis, "DOMParser">;
5
22
  /** The parser implementation; defaults to `new window.DOMParser()`. */
6
23
  parser?: DOMParser;
7
- /** The serializer implementation; defaults to `new window.XMLSerializer()`. */
8
- serializer?: XMLSerializer;
9
24
  /** The path to the page template; defaults to the default template. */
10
25
  template?: string;
26
+ /** An optional function which transforms the template HTML for the current page. */
27
+ transformTemplate?: TemplateTransform;
28
+ /** An optional function which transforms the notebook for the current page. */
29
+ transformNotebook?: NotebookTransform;
11
30
  }
12
- export declare function observable({ window, parser, serializer, template }?: ObservableOptions): PluginOption;
31
+ export declare function observable({ window, parser, template, transformTemplate, transformNotebook }?: ObservableOptions): PluginOption;
@@ -1,8 +1,10 @@
1
1
  import { existsSync } from "node:fs";
2
- import { readFile } from "node:fs/promises";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { dirname, join, resolve } from "node:path";
4
+ import { relative } from "node:path/posix";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { JSDOM } from "jsdom";
7
+ import { getDatabase, getDatabaseConfig, getQueryCachePath } from "../databases/index.js";
6
8
  import { deserialize } from "../lib/serialize.js";
7
9
  import { Sourcemap } from "../javascript/sourcemap.js";
8
10
  import { transpile } from "../javascript/transpile.js";
@@ -10,7 +12,7 @@ import { parseTemplate } from "../javascript/template.js";
10
12
  import { collectAssets } from "../runtime/stdlib/assets.js";
11
13
  import { highlight } from "../runtime/stdlib/highlight.js";
12
14
  import { MarkdownRenderer } from "../runtime/stdlib/md.js";
13
- export function observable({ window = new JSDOM().window, parser = new window.DOMParser(), serializer = new window.XMLSerializer(), template = fileURLToPath(import.meta.resolve("../templates/default.html")) } = {}) {
15
+ export function observable({ window = new JSDOM().window, parser = new window.DOMParser(), template = fileURLToPath(import.meta.resolve("../templates/default.html")), transformTemplate = (template) => template, transformNotebook = (notebook) => notebook } = {}) {
14
16
  return {
15
17
  name: "observable",
16
18
  buildStart() {
@@ -24,9 +26,9 @@ export function observable({ window = new JSDOM().window, parser = new window.DO
24
26
  transformIndexHtml: {
25
27
  order: "pre",
26
28
  async handler(input, context) {
27
- const notebook = deserialize(input, { parser });
28
- const tsource = await readFile(template, "utf-8");
29
- const document = parser.parseFromString(tsource, "text/html");
29
+ const notebook = await transformNotebook(deserialize(input, { parser }), context);
30
+ const templateHtml = await transformTemplate(await readFile(template, "utf-8"), context);
31
+ const document = parser.parseFromString(templateHtml, "text/html");
30
32
  const statics = new Set();
31
33
  const assets = new Set();
32
34
  const md = MarkdownRenderer({ document });
@@ -41,26 +43,47 @@ export function observable({ window = new JSDOM().window, parser = new window.DO
41
43
  let cells = document.querySelector("main");
42
44
  cells ?? (cells = document.body.appendChild(document.createElement("main")));
43
45
  for (const cell of notebook.cells) {
44
- const { id, mode, pinned, value } = cell;
46
+ const { id, mode, pinned, hidden, value } = cell;
45
47
  const contents = document.createDocumentFragment();
46
48
  const div = contents.appendChild(document.createElement("div"));
47
49
  div.id = `cell-${id}`;
48
50
  div.className = "observablehq observablehq--cell";
49
- if (mode === "md") {
51
+ if (mode === "md" && !hidden) {
50
52
  const template = parseTemplate(value);
51
- if (!template.expressions.length)
53
+ if (!template.expressions.length && !cell.output)
52
54
  statics.add(cell);
53
- const content = md([stripExpressions(template, value)]);
55
+ const content = md([unescapeDollarBackslashCurly(stripExpressions(template, value))]);
54
56
  const codes = content.querySelectorAll("code[class^=language-]");
55
57
  await Promise.all(Array.from(codes, highlight));
56
58
  div.appendChild(content);
57
59
  }
58
- else if (mode === "html") {
60
+ else if (mode === "html" && !hidden) {
59
61
  const template = parseTemplate(value);
60
- if (!template.expressions.length)
62
+ if (!template.expressions.length && !cell.output)
61
63
  statics.add(cell);
62
64
  div.innerHTML = stripExpressions(template, value);
63
65
  }
66
+ else if (mode === "sql" && cell.database && !cell.database.startsWith("var:")) {
67
+ const template = parseTemplate(value);
68
+ if (!template.expressions.length) {
69
+ const dir = dirname(context.filename);
70
+ const cachePath = await getQueryCachePath(context.filename, cell.database, [value]);
71
+ if (!existsSync(cachePath)) {
72
+ const config = await getDatabaseConfig(context.filename, cell.database);
73
+ try {
74
+ const database = await getDatabase(config, { cwd: dir });
75
+ const results = await database.call(null, [value]);
76
+ await mkdir(dirname(cachePath), { recursive: true });
77
+ await writeFile(cachePath, JSON.stringify(results));
78
+ }
79
+ catch (error) {
80
+ console.error(error);
81
+ }
82
+ }
83
+ cell.mode = "js";
84
+ cell.value = `FileAttachment(${JSON.stringify(relative(dir, cachePath))}).json().then(DatabaseClient.revive)${hidden ? "" : `.then(Inputs.table)${cell.output ? ".then(view)" : ""}`}`;
85
+ }
86
+ }
64
87
  collectAssets(assets, div);
65
88
  if (pinned) {
66
89
  const pre = contents.appendChild(document.createElement("pre"));
@@ -73,15 +96,16 @@ export function observable({ window = new JSDOM().window, parser = new window.DO
73
96
  }
74
97
  // Don’t error if assets are missing (matching Vite’s behavior).
75
98
  filterMissingAssets(assets, dirname(context.filename));
76
- const output = serializer.serializeToString(document);
99
+ const output = document.documentElement.outerHTML;
77
100
  const i = output.indexOf("</body>");
78
101
  if (!(i >= 0))
79
102
  throw new Error("body not found");
80
- return (output.slice(0, i) +
103
+ return (`<!doctype html>` +
104
+ output.slice(0, i) +
81
105
  `<style type="text/css">
82
106
  @import url("observable:styles/theme-${notebook.theme}.css");
83
107
  </style><script type="module">
84
- import {define} from "observable:runtime/define";${Array.from(assets)
108
+ import {define} from "observable:runtime";${Array.from(assets)
85
109
  .map((asset, i) => `
86
110
  import asset${i + 1} from ${JSON.stringify(`${asset}?url`)};`)
87
111
  .join("")}${assets.size > 0
@@ -96,7 +120,7 @@ ${Array.from(assets)
96
120
  ${notebook.cells
97
121
  .filter((cell) => !statics.has(cell))
98
122
  .map((cell) => {
99
- const transpiled = transpile(cell.value, cell.mode, { resolveFiles: true });
123
+ const transpiled = transpile(cell, { resolveFiles: true });
100
124
  return `
101
125
  define(
102
126
  {
@@ -154,6 +178,9 @@ function stripExpressions(template, input) {
154
178
  }
155
179
  return String(source);
156
180
  }
181
+ function unescapeDollarBackslashCurly(input) {
182
+ return input.replace(/(\$\\*)\\({)/g, "$1$2");
183
+ }
157
184
  /** Returns true if the specified character is preceded by an equals sign, ignoring whitespace. */
158
185
  function hasPrecedingEquals(input, index) {
159
186
  let i = index - 1;
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.0.1",
8
+ "version": "1.1.0-rc.10",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "test": "vitest",
@@ -13,7 +13,7 @@
13
13
  "lint": "tsc --noEmit && eslint bin src types",
14
14
  "notebooks": "tsx bin/notebooks.ts",
15
15
  "download": "tsx bin/notebooks.ts download",
16
- "docs:preview": "tsx --watch bin/notebooks.ts preview --root docs --template docs/observable.tmpl",
16
+ "docs:preview": "tsx --watch bin/notebooks.ts preview --base /notebook-kit/ --root docs --template docs/observable.tmpl",
17
17
  "docs:build": "tsx bin/notebooks.ts build --root docs --template docs/observable.tmpl -- $(find docs -path 'docs/.observable' -prune -o -name '*.html' -print)"
18
18
  },
19
19
  "bin": {
@@ -27,6 +27,10 @@
27
27
  "types": "./dist/src/index.d.ts",
28
28
  "import": "./dist/src/index.js"
29
29
  },
30
+ "./databases": {
31
+ "types": "./dist/src/databases/index.d.ts",
32
+ "import": "./dist/src/databases/index.js"
33
+ },
30
34
  "./runtime": {
31
35
  "types": "./dist/src/runtime/index.d.ts",
32
36
  "import": "./dist/src/runtime/index.js"
@@ -59,15 +63,40 @@
59
63
  "vite": "^7.0.0"
60
64
  },
61
65
  "devDependencies": {
66
+ "@duckdb/node-api": "^1.3.2-alpha.26",
62
67
  "@eslint/js": "^9.29.0",
68
+ "@types/better-sqlite3": "^7.6.13",
63
69
  "@types/jsdom": "^21.1.7",
64
70
  "@types/markdown-it": "^14.1.2",
71
+ "better-sqlite3": "^12.2.0",
65
72
  "eslint": "^9.29.0",
66
73
  "globals": "^16.2.0",
67
74
  "htl": "^0.3.1",
75
+ "postgres": "^3.4.7",
76
+ "snowflake-sdk": "^2.1.3",
68
77
  "tsx": "^4.20.3",
69
78
  "typescript": "^5.8.3",
70
79
  "typescript-eslint": "^8.35.0",
71
80
  "vitest": "^3.2.4"
81
+ },
82
+ "peerDependencies": {
83
+ "@duckdb/node-api": "^1.3.2-alpha.26",
84
+ "better-sqlite3": "^12.2.0",
85
+ "postgres": "^3.4.7",
86
+ "snowflake-sdk": "^2.1.3"
87
+ },
88
+ "peerDependenciesMeta": {
89
+ "@duckdb/node-api": {
90
+ "optional": true
91
+ },
92
+ "better-sqlite3": {
93
+ "optional": true
94
+ },
95
+ "postgres": {
96
+ "optional": true
97
+ },
98
+ "snowflake-sdk": {
99
+ "optional": true
100
+ }
72
101
  }
73
102
  }
@@ -1,5 +0,0 @@
1
- type Template = (template: TemplateStringsArray, ...values: unknown[]) => Promise<unknown>;
2
- export declare function __sql(db: {
3
- sql: Template;
4
- }, render: (data: unknown) => unknown): Template;
5
- export {};
@@ -1,5 +0,0 @@
1
- export function __sql(db, render) {
2
- return (template, ...values) => {
3
- return db.sql.call(db, template, ...values).then(render);
4
- };
5
- }