@malloydata/db-duckdb 0.0.406 → 0.0.408
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/dist/duckdb_config.js
CHANGED
|
@@ -47,6 +47,7 @@ exports.sqlStringLiteral = sqlStringLiteral;
|
|
|
47
47
|
exports.sqlStringListLiteral = sqlStringListLiteral;
|
|
48
48
|
exports.stringifyDuckDBOption = stringifyDuckDBOption;
|
|
49
49
|
const path_1 = __importDefault(require("path"));
|
|
50
|
+
const url_1 = require("url");
|
|
50
51
|
const malloy_1 = require("@malloydata/malloy");
|
|
51
52
|
const pathSecurity = __importStar(require("./path_security"));
|
|
52
53
|
class DuckDBConfigValidationError extends Error {
|
|
@@ -127,7 +128,7 @@ function normalizeDuckDBConfig(config) {
|
|
|
127
128
|
mustExist: securityPolicy === 'sandboxed',
|
|
128
129
|
});
|
|
129
130
|
}
|
|
130
|
-
const databasePath = canonicalizeDatabasePath(rawDatabasePath);
|
|
131
|
+
const databasePath = canonicalizeDatabasePath(rawDatabasePath, workingDirectory);
|
|
131
132
|
const isMotherDuck = isMotherDuckPath(databasePath);
|
|
132
133
|
if (restricted && !isAllowedClosedNetworkDatabasePath(databasePath)) {
|
|
133
134
|
throw new DuckDBConfigValidationError(`databasePath "${rawDatabasePath}" is not allowed when securityPolicy is "${securityPolicy}"`);
|
|
@@ -342,15 +343,46 @@ function deriveRestrictedSecretDirectory({ requiresSecretNeutralization, tempDir
|
|
|
342
343
|
}
|
|
343
344
|
throw new DuckDBConfigValidationError('restricted DuckDB policies require workingDirectory or tempDirectory so Malloy can isolate persistent DuckDB secrets');
|
|
344
345
|
}
|
|
345
|
-
|
|
346
|
+
// A relative `databasePath` resolves against `workingDirectory` (which itself
|
|
347
|
+
// defaults to the project root via `{config: 'rootDirectory'}`), keeping a
|
|
348
|
+
// config file portable — `databasePath: "analytics.duckdb"` names the database
|
|
349
|
+
// alongside the project regardless of where the host process is launched.
|
|
350
|
+
// `:memory:` and remote schemes are taken as-is; absolute paths ignore the
|
|
351
|
+
// base; with no `workingDirectory` set, a relative path falls back to the cwd.
|
|
352
|
+
function canonicalizeDatabasePath(databasePath, workingDirectory) {
|
|
346
353
|
if (databasePath === ':memory:' || isLikelyRemoteDatabasePath(databasePath)) {
|
|
347
354
|
return databasePath;
|
|
348
355
|
}
|
|
349
|
-
return canonicalizeConfigPath(databasePath, 'databasePath'
|
|
356
|
+
return canonicalizeConfigPath(databasePath, 'databasePath', {
|
|
357
|
+
baseDirectory: workingDirectory,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
// A config path can arrive as a `file://` URL rather than a plain path —
|
|
361
|
+
// notably `workingDirectory`, which defaults to `config.rootDirectory` (the
|
|
362
|
+
// config stack carries it as a URL string). Left as a URL, `path.resolve`
|
|
363
|
+
// treats `file:` as a relative segment and joins it to the process cwd,
|
|
364
|
+
// silently breaking relative `read_parquet`/glob resolution. Decode file URLs
|
|
365
|
+
// to a real path so every downstream consumer (FILE_SEARCH_PATH,
|
|
366
|
+
// allowed_directories, …) sees one. Plain paths and non-file schemes pass
|
|
367
|
+
// through untouched; a malformed file URL throws and surfaces as the caller's
|
|
368
|
+
// field-specific validation error.
|
|
369
|
+
function maybeFileURLToPath(input) {
|
|
370
|
+
// Cheap guard so plain paths (the common case) skip URL parsing entirely.
|
|
371
|
+
if (!/^[a-z][a-z0-9+.-]*:/i.test(input)) {
|
|
372
|
+
return input;
|
|
373
|
+
}
|
|
374
|
+
let url;
|
|
375
|
+
try {
|
|
376
|
+
url = new URL(input);
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return input; // not a URL — treat as a plain path
|
|
380
|
+
}
|
|
381
|
+
return url.protocol === 'file:' ? (0, url_1.fileURLToPath)(url) : input;
|
|
350
382
|
}
|
|
351
383
|
function canonicalizeConfigPath(input, fieldName, options = {}) {
|
|
352
384
|
try {
|
|
353
|
-
return pathSecurity.canonicalizePath(input, options);
|
|
385
|
+
return pathSecurity.canonicalizePath(maybeFileURLToPath(input), options);
|
|
354
386
|
}
|
|
355
387
|
catch (error) {
|
|
356
388
|
throw new DuckDBConfigValidationError(`${fieldName} is invalid: ${errorMessage(error)}`);
|
|
@@ -214,6 +214,22 @@ function unwrapTable(table) {
|
|
|
214
214
|
return table.toArray().map(row => unwrapRow(row, table.schema));
|
|
215
215
|
}
|
|
216
216
|
const isNode = () => { var _a; return typeof process !== 'undefined' && typeof ((_a = process.versions) === null || _a === void 0 ? void 0 : _a.node) === 'string'; };
|
|
217
|
+
// `workingDirectory` defaults to `config.rootDirectory`, which the config
|
|
218
|
+
// stack carries as a URL string. DuckDB-Wasm's virtual filesystem wants a plain
|
|
219
|
+
// POSIX path; a `file://` URL left intact becomes a bogus FILE_SEARCH_PATH and
|
|
220
|
+
// breaks relative reads. Decode file URLs to their path. Browser-safe — uses
|
|
221
|
+
// the WHATWG `URL` (the native connection's Node `fileURLToPath` is unavailable
|
|
222
|
+
// here). Plain paths and non-file schemes pass through untouched.
|
|
223
|
+
function fileURLToVirtualPath(input) {
|
|
224
|
+
if (!/^file:\/\//i.test(input)) {
|
|
225
|
+
return input;
|
|
226
|
+
}
|
|
227
|
+
const decoded = decodeURIComponent(new URL(input).pathname);
|
|
228
|
+
// Drop a trailing separator but keep the filesystem root '/'.
|
|
229
|
+
return decoded.length > 1 && decoded.endsWith('/')
|
|
230
|
+
? decoded.slice(0, -1)
|
|
231
|
+
: decoded;
|
|
232
|
+
}
|
|
217
233
|
class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
|
|
218
234
|
constructor(arg, arg2, workingDirectory, queryOptions) {
|
|
219
235
|
var _a, _b;
|
|
@@ -233,7 +249,7 @@ class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
|
|
|
233
249
|
this.databasePath = arg2;
|
|
234
250
|
}
|
|
235
251
|
if (typeof workingDirectory === 'string') {
|
|
236
|
-
this.workingDirectory = workingDirectory;
|
|
252
|
+
this.workingDirectory = fileURLToVirtualPath(workingDirectory);
|
|
237
253
|
}
|
|
238
254
|
if (queryOptions) {
|
|
239
255
|
this.queryOptions = queryOptions;
|
|
@@ -248,7 +264,7 @@ class DuckDBWASMConnection extends duckdb_common_1.DuckDBCommon {
|
|
|
248
264
|
this.databasePath = arg.databasePath;
|
|
249
265
|
}
|
|
250
266
|
if (typeof arg.workingDirectory === 'string') {
|
|
251
|
-
this.workingDirectory = arg.workingDirectory;
|
|
267
|
+
this.workingDirectory = fileURLToVirtualPath(arg.workingDirectory);
|
|
252
268
|
}
|
|
253
269
|
if (typeof arg.motherDuckToken === 'string') {
|
|
254
270
|
this.motherDuckToken = arg.motherDuckToken;
|
package/dist/path_security.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export interface CanonicalPathOptions {
|
|
2
2
|
mustExist?: boolean;
|
|
3
|
+
baseDirectory?: string;
|
|
3
4
|
}
|
|
4
5
|
export declare function isPosixHost(): boolean;
|
|
5
|
-
export declare function canonicalizePath(input: string, { mustExist }?: CanonicalPathOptions): string;
|
|
6
|
+
export declare function canonicalizePath(input: string, { mustExist, baseDirectory }?: CanonicalPathOptions): string;
|
|
6
7
|
export declare function canonicalizePathList(paths: string[]): string[];
|
|
7
8
|
export declare function isContainedPath(parent: string, child: string): boolean;
|
package/dist/path_security.js
CHANGED
|
@@ -16,11 +16,13 @@ const path_1 = __importDefault(require("path"));
|
|
|
16
16
|
function isPosixHost() {
|
|
17
17
|
return path_1.default.sep === '/';
|
|
18
18
|
}
|
|
19
|
-
function canonicalizePath(input, { mustExist = false } = {}) {
|
|
19
|
+
function canonicalizePath(input, { mustExist = false, baseDirectory } = {}) {
|
|
20
20
|
if (input.trim() === '') {
|
|
21
21
|
throw new Error('path must not be empty');
|
|
22
22
|
}
|
|
23
|
-
const resolved =
|
|
23
|
+
const resolved = baseDirectory !== undefined
|
|
24
|
+
? path_1.default.resolve(baseDirectory, input)
|
|
25
|
+
: path_1.default.resolve(input);
|
|
24
26
|
const canonical = (() => {
|
|
25
27
|
try {
|
|
26
28
|
return fs_1.default.realpathSync.native(resolved);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/db-duckdb",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.408",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@duckdb/duckdb-wasm": "1.33.1-dev45.0",
|
|
63
63
|
"@duckdb/node-api": "1.5.3-r.2",
|
|
64
|
-
"@malloydata/malloy": "0.0.
|
|
64
|
+
"@malloydata/malloy": "0.0.408",
|
|
65
65
|
"@motherduck/wasm-client": "^0.6.6",
|
|
66
66
|
"apache-arrow": "^17.0.0",
|
|
67
67
|
"web-worker": "^1.5.0"
|