-
Notifications
You must be signed in to change notification settings - Fork 177
duckdb 1.29.0; self-host extensions #1734
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
Changes from 39 commits
c70e1bc
0029c8c
feeaad8
33aa5cb
543f823
7475589
47b6bd0
abd0380
7ac5d1d
0adcb36
5365371
1fdf717
bc712c3
2fb2878
bc49674
9a13f2a
e2c8b6c
4a5128d
13f892c
8bb2866
43ef6eb
6764969
d72f0c3
d6fc020
69f25a2
30788e3
710f36a
4f58100
a8cfdcd
490d969
aaff8f8
bc39bbe
8bd0972
b90c22a
b37be07
9abaf57
ccc0073
26c7a6f
4416dd3
7704416
1dde616
0491966
be26385
a23d3e4
c753728
6e828c9
365dbe3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import {existsSync} from "node:fs"; | |
| import {copyFile, readFile, rm, stat, writeFile} from "node:fs/promises"; | ||
| import {basename, dirname, extname, join} from "node:path/posix"; | ||
| import type {Config} from "./config.js"; | ||
| import {getDuckDBManifest} from "./duckdb.js"; | ||
| import {CliError} from "./error.js"; | ||
| import {getClientPath, prepareOutput} from "./files.js"; | ||
| import {findModule, getModuleHash, readJavaScript} from "./javascript/module.js"; | ||
|
|
@@ -53,7 +54,7 @@ export async function build( | |
| {config}: BuildOptions, | ||
| effects: BuildEffects = new FileBuildEffects(config.output, join(config.root, ".observablehq", "cache")) | ||
| ): Promise<void> { | ||
| const {root, loaders} = config; | ||
| const {root, loaders, duckdb} = config; | ||
| Telemetry.record({event: "build", step: "start"}); | ||
|
|
||
| // Prepare for build (such as by emptying the existing output root). | ||
|
|
@@ -140,6 +141,20 @@ export async function build( | |
| effects.logger.log(cachePath); | ||
| } | ||
|
|
||
| // Copy over the DuckDB extensions and create the DuckDB manifest. | ||
| for (const path of globalImports) { | ||
| if (path.startsWith("/_duckdb/")) { | ||
| const sourcePath = join(cacheRoot, path); | ||
| effects.output.write(`${faint("build")} ${path} ${faint("→")} `); | ||
| const contents = await readFile(sourcePath); | ||
| const hash = createHash("sha256").update(contents).digest("hex").slice(0, 8); | ||
| const alias = applyHash(path, hash); | ||
| aliases.set(path, alias); | ||
| await effects.writeFile(alias, contents); | ||
| } | ||
| } | ||
| const duckDBManifest = await getDuckDBManifest(duckdb, {root, aliases}); | ||
|
|
||
| // Generate the client bundles. These are initially generated into the cache | ||
| // because we need to rewrite any npm and node imports to be hashed; this is | ||
| // handled generally for all global imports below. | ||
|
|
@@ -149,6 +164,9 @@ export async function build( | |
| effects.output.write(`${faint("bundle")} ${path} ${faint("→")} `); | ||
| const clientPath = getClientPath(path === "/_observablehq/client.js" ? "index.js" : path.slice("/_observablehq/".length)); // prettier-ignore | ||
| const define: {[key: string]: string} = {}; | ||
| if (path === "/_observablehq/stdlib/duckdb.js") { | ||
| define["DUCKDB_MANIFEST"] = JSON.stringify(duckDBManifest); | ||
| } | ||
| const contents = await rollupClient(clientPath, root, path, {minify: true, keepNames: true, define}); | ||
| await prepareOutput(cachePath); | ||
| await writeFile(cachePath, contents); | ||
|
|
@@ -204,7 +222,7 @@ export async function build( | |
| // Anything in _observablehq also needs a content hash, but anything in _npm | ||
| // or _node does not (because they are already necessarily immutable). | ||
| for (const path of globalImports) { | ||
| if (path.endsWith(".js")) continue; | ||
| if (path.endsWith(".js") || path.startsWith("/_duckdb/")) continue; | ||
mbostock marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const sourcePath = join(cacheRoot, path); | ||
| effects.output.write(`${faint("build")} ${path} ${faint("→")} `); | ||
| if (path.startsWith("/_observablehq/")) { | ||
|
|
@@ -398,6 +416,7 @@ function validateLinks(outputs: Map<string, {resolvers: Resolvers}>): [valid: Li | |
| } | ||
|
|
||
| function applyHash(path: string, hash: string): string { | ||
| if (path.startsWith("/_duckdb/")) return join("/_duckdb/", `${hash}-${path.slice("/_duckdb/".length)}`); | ||
|
||
| const ext = extname(path); | ||
| let name = basename(path, ext); | ||
| if (path.endsWith(".js")) name = name.replace(/(^|\.)_esm$/, ""); // allow hash to replace _esm | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,16 +29,26 @@ import * as duckdb from "npm:@duckdb/duckdb-wasm"; | |
| // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
| // POSSIBILITY OF SUCH DAMAGE. | ||
|
|
||
| const bundle = await duckdb.selectBundle({ | ||
| mvp: { | ||
| mainModule: import.meta.resolve("npm:@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm"), | ||
| mainWorker: import.meta.resolve("npm:@duckdb/duckdb-wasm/dist/duckdb-browser-mvp.worker.js") | ||
| }, | ||
| eh: { | ||
| mainModule: import.meta.resolve("npm:@duckdb/duckdb-wasm/dist/duckdb-eh.wasm"), | ||
| mainWorker: import.meta.resolve("npm:@duckdb/duckdb-wasm/dist/duckdb-browser-eh.worker.js") | ||
| } | ||
| }); | ||
| // Baked-in manifest. | ||
| // eslint-disable-next-line no-undef | ||
| const manifest = DUCKDB_MANIFEST; | ||
|
|
||
| const candidates = { | ||
| ...(manifest.bundles.includes("mvp") && { | ||
| mvp: { | ||
| mainModule: import.meta.resolve("npm:@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm"), | ||
| mainWorker: import.meta.resolve("npm:@duckdb/duckdb-wasm/dist/duckdb-browser-mvp.worker.js") | ||
| } | ||
| }), | ||
| ...(manifest.bundles.includes("eh") && { | ||
| eh: { | ||
| mainModule: import.meta.resolve("npm:@duckdb/duckdb-wasm/dist/duckdb-eh.wasm"), | ||
| mainWorker: import.meta.resolve("npm:@duckdb/duckdb-wasm/dist/duckdb-browser-eh.worker.js") | ||
| } | ||
| }) | ||
mbostock marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
| const bundle = await duckdb.selectBundle(candidates); | ||
| const activePlatform = manifest.bundles.find((key) => bundle.mainModule === candidates[key].mainModule); | ||
|
|
||
| const logger = new duckdb.ConsoleLogger(duckdb.LogLevel.WARNING); | ||
|
|
||
|
|
@@ -169,6 +179,7 @@ export class DuckDBClient { | |
| config = {...config, query: {...config.query, castBigIntToDouble: true}}; | ||
| } | ||
| await db.open(config); | ||
| await registerExtensions(db, config); | ||
| await Promise.all(Object.entries(sources).map(([name, source]) => insertSource(db, name, source))); | ||
| return new DuckDBClient(db); | ||
| } | ||
|
|
@@ -178,9 +189,22 @@ export class DuckDBClient { | |
| } | ||
| } | ||
|
|
||
| Object.defineProperty(DuckDBClient.prototype, "dialect", { | ||
| value: "duckdb" | ||
| }); | ||
| Object.defineProperty(DuckDBClient.prototype, "dialect", {value: "duckdb"}); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The DatabaseClient.dialect isn’t used for anything in Framework. I think it’s used in notebooks for ejecting from a SQL cell, but there’s no analogous concept in Framework so maybe we should consider dropping it. Not directly related to this PR though! |
||
|
|
||
| async function registerExtensions(db, {load}) { | ||
| const connection = await db.connect(); | ||
| try { | ||
| await Promise.all( | ||
| manifest.extensions.map(([name, {[activePlatform]: ref, load: l}]) => | ||
| connection | ||
| .query(`INSTALL "${name}" FROM '${ref.startsWith("https://") ? ref : import.meta.resolve(`../..${ref}`)}'`) | ||
| .then(() => (load ? load.includes(name) : l) && connection.query(`LOAD "${name}"`)) | ||
| ) | ||
| ); | ||
| } finally { | ||
| await connection.close(); | ||
| } | ||
| } | ||
|
|
||
| async function insertSource(database, name, source) { | ||
| source = await source; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.