@sixb/ducklake 0.2.1 → 0.2.2

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.
package/README.md CHANGED
@@ -65,6 +65,18 @@ const lakeStorage = new DuckLakeStorage({
65
65
  Use `close()` during service shutdown so DuckDB can close its runtime and release
66
66
  catalog connections cleanly.
67
67
 
68
+ ### Extensions
69
+
70
+ On first use, the provider installs DuckLake and the extensions its catalog,
71
+ data path and secrets need. A missing extension is downloaded over HTTPS from
72
+ `https://extensions.duckdb.org` into DuckDB's extension directory
73
+ (`duckdb.config.extension_directory`, `~/.duckdb/extensions` by default), so a
74
+ host that allows only that HTTPS domain, such as a restricted Vercel sandbox,
75
+ can open a lake. When that download fails, the provider logs a warning and
76
+ DuckDB's own `INSTALL` downloads the extension over plain HTTP, as before.
77
+ With `custom_extension_repository` set, DuckDB installs from that repository
78
+ alone.
79
+
68
80
  ### Maintenance
69
81
 
70
82
  DuckLake keeps historical snapshots and deleted files until maintenance runs.
package/dist/index.js CHANGED
@@ -871,13 +871,78 @@ function localCatalogCoordinationKey(options) {
871
871
  }
872
872
 
873
873
  // src/internal/duckdb-runtime.ts
874
- import { mkdir, mkdtemp, rm } from "fs/promises";
875
- import { join, resolve as resolve2 } from "path";
874
+ import { mkdir, mkdtemp as mkdtemp2, rm as rm2 } from "fs/promises";
875
+ import { join as join2, resolve as resolve2 } from "path";
876
876
  import {
877
877
  DuckDBInstance
878
878
  } from "@duckdb/node-api";
879
879
  import { LakeStorageError as LakeStorageError11 } from "@sixb/core/lake-storage";
880
880
 
881
+ // src/internal/duckdb-extensions.ts
882
+ import { mkdtemp, rm, writeFile } from "fs/promises";
883
+ import { tmpdir } from "os";
884
+ import { join } from "path";
885
+ import { promisify } from "util";
886
+ import { gunzip } from "zlib";
887
+ var extensionsUrl = "https://extensions.duckdb.org";
888
+ var downloadTimeoutMillis = 60000;
889
+ var gunzipAsync = promisify(gunzip);
890
+ async function prefetchDuckDbExtensions(runtime, extensions) {
891
+ await prefetch(runtime, extensions).catch((error) => warnFallback(extensions.join("', '"), error));
892
+ }
893
+ async function prefetch(runtime, extensions) {
894
+ const [target] = await runtime.query(`
895
+ SELECT
896
+ version() AS version,
897
+ (SELECT platform FROM pragma_platform()) AS platform,
898
+ current_setting('custom_extension_repository') AS repository
899
+ `);
900
+ if (target?.repository !== "")
901
+ return;
902
+ const missing = await missingExtensions(runtime, extensions);
903
+ if (missing.length === 0)
904
+ return;
905
+ const directory = await mkdtemp(join(tmpdir(), "sixb-duckdb-extensions-"));
906
+ try {
907
+ const downloads = await Promise.all(missing.map(async (name) => ({
908
+ name,
909
+ file: await download(`${target.version}/${target.platform}/${name}`, directory, name).catch((error) => warnFallback(name, error))
910
+ })));
911
+ for (const { name, file } of downloads) {
912
+ if (file === undefined)
913
+ continue;
914
+ await runtime.run(`INSTALL ${quoteSqlString(file)}`).catch((error) => warnFallback(name, error));
915
+ }
916
+ } finally {
917
+ await rm(directory, { recursive: true, force: true });
918
+ }
919
+ }
920
+ function warnFallback(name, error) {
921
+ const reason = error instanceof Error ? error.message : String(error);
922
+ console.warn(`[SixbDuckLake] Could not install DuckDB extension '${name}' over HTTPS (${reason}); falling back to DuckDB's INSTALL.`);
923
+ return;
924
+ }
925
+ async function missingExtensions(runtime, extensions) {
926
+ const requested = `[${quoteSqlStringList(extensions.map((extension) => extension.toLowerCase()))}]`;
927
+ const rows = await runtime.query(`
928
+ SELECT extension_name
929
+ FROM duckdb_extensions()
930
+ WHERE NOT installed
931
+ AND (list_contains(${requested}, extension_name) OR list_has_any(${requested}, aliases))
932
+ `);
933
+ return rows.map((row) => String(row.extension_name));
934
+ }
935
+ async function download(path, directory, name) {
936
+ const response = await fetch(`${extensionsUrl}/${path}.duckdb_extension.gz`, {
937
+ signal: AbortSignal.timeout(downloadTimeoutMillis)
938
+ });
939
+ if (!response.ok)
940
+ throw new Error(`HTTP ${response.status}`);
941
+ const file = join(directory, `${name}.duckdb_extension`);
942
+ await writeFile(file, await gunzipAsync(new Uint8Array(await response.arrayBuffer())));
943
+ return file;
944
+ }
945
+
881
946
  // src/internal/duckdb-reader.ts
882
947
  import { LakeStorageError as LakeStorageError10 } from "@sixb/core/lake-storage";
883
948
 
@@ -1094,7 +1159,7 @@ class NodeDuckDbRuntime {
1094
1159
  this.connection.closeSync();
1095
1160
  this.instance.closeSync();
1096
1161
  if (this.temporaryDirectory)
1097
- await rm(this.temporaryDirectory, { recursive: true, force: true });
1162
+ await rm2(this.temporaryDirectory, { recursive: true, force: true });
1098
1163
  const failed = results.find((result) => result.status === "rejected");
1099
1164
  if (failed?.status === "rejected")
1100
1165
  throw failed.reason;
@@ -1181,7 +1246,7 @@ async function createDuckDbRuntime(options = {}) {
1181
1246
  if (base !== "") {
1182
1247
  const directory = resolve2(base);
1183
1248
  await mkdir(directory, { recursive: true });
1184
- temporaryDirectory = await mkdtemp(join(directory, "sixb-"));
1249
+ temporaryDirectory = await mkdtemp2(join2(directory, "sixb-"));
1185
1250
  }
1186
1251
  let instance;
1187
1252
  try {
@@ -1193,13 +1258,15 @@ async function createDuckDbRuntime(options = {}) {
1193
1258
  } catch (error) {
1194
1259
  instance?.closeSync();
1195
1260
  if (temporaryDirectory)
1196
- await rm(temporaryDirectory, { recursive: true, force: true });
1261
+ await rm2(temporaryDirectory, { recursive: true, force: true });
1197
1262
  throw error;
1198
1263
  }
1199
1264
  }
1200
1265
  async function installDuckLakeExtensions(runtime, options) {
1266
+ const extensions = requiredExtensions(options);
1267
+ await prefetchDuckDbExtensions(runtime, ["ducklake", ...extensions]);
1201
1268
  await runtime.run("INSTALL ducklake");
1202
- for (const extension of requiredExtensions(options)) {
1269
+ for (const extension of extensions) {
1203
1270
  await runtime.run(`INSTALL ${quoteIdentifier(extension)}`);
1204
1271
  }
1205
1272
  }
@@ -5812,4 +5879,4 @@ export {
5812
5879
  DuckLakeStorage
5813
5880
  };
5814
5881
 
5815
- //# debugId=8FEE4C4A10B2BA3064756E2164756E21
5882
+ //# debugId=11CCDDEF58B2058164756E2164756E21