@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 +12 -0
- package/dist/index.js +74 -7
- package/dist/index.js.map +5 -4
- package/dist/internal/duckdb-extensions.d.ts +15 -0
- package/dist/internal/duckdb-extensions.d.ts.map +1 -0
- package/dist/internal/duckdb-runtime.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/internal/duckdb-extensions.ts +102 -0
- package/src/internal/duckdb-runtime.ts +5 -1
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
|
|
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
|
|
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
|
|
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
|
|
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=
|
|
5882
|
+
//# debugId=11CCDDEF58B2058164756E2164756E21
|