@flowblade/sqlduck 0.21.0 → 0.23.0

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.
@@ -0,0 +1,85 @@
1
+ import { getLogger } from "@logtape/logtape";
2
+ import fs from "node:fs";
3
+ import { basename, dirname } from "node:path";
4
+ //#region src/config/flowblade-logtape-sqlduck.config.ts
5
+ const flowbladeLogtapeSqlduckConfig = { categories: ["flowblade", "sqlduck"] };
6
+ //#endregion
7
+ //#region src/logger/sqlduck-default-logtape-logger.ts
8
+ const sqlduckDefaultLogtapeLogger = getLogger(flowbladeLogtapeSqlduckConfig.categories);
9
+ //#endregion
10
+ //#region src/filesystem/file-system-utils.ts
11
+ var FileSystemUtils = class {
12
+ #logger;
13
+ constructor(params) {
14
+ this.#logger = params?.logger ?? sqlduckDefaultLogtapeLogger.with({ source: "FileSystemUtils" });
15
+ }
16
+ /**
17
+ * Create a directory recursively if it doesn't exist
18
+ *
19
+ * @throws Error if it can't be created
20
+ */
21
+ createDirectory = (path) => {
22
+ try {
23
+ fs.mkdirSync(path, { recursive: true });
24
+ } catch (err) {
25
+ if (err.code !== "EEXIST") throw err;
26
+ }
27
+ };
28
+ /**
29
+ * Create a directory recursively if it doesn't exist and ensure it's writable
30
+ */
31
+ createAndEnsureWritableDirectory = (label, path) => {
32
+ if (path === void 0) return;
33
+ if (!fs.existsSync(path)) try {
34
+ this.createDirectory(path);
35
+ } catch (e) {
36
+ throw new Error(`Failed to create ${label} '${path}' - ${e?.message ?? ""}`);
37
+ }
38
+ if (!fs.statSync(path).isDirectory()) throw new Error(`${label} '${path}' must be a directory`);
39
+ try {
40
+ fs.accessSync(path, fs.constants.W_OK);
41
+ } catch {
42
+ throw new Error(`${label} '${path}' must be writable`);
43
+ }
44
+ };
45
+ /**
46
+ * Check if a path is a regular file and exists
47
+ */
48
+ isFile = (path) => {
49
+ try {
50
+ return fs.statSync(path).isFile();
51
+ } catch {
52
+ return false;
53
+ }
54
+ };
55
+ /**
56
+ * Check if a path is a directory and exists
57
+ */
58
+ isDirectory = (path) => {
59
+ try {
60
+ return fs.statSync(path).isDirectory();
61
+ } catch {
62
+ return false;
63
+ }
64
+ };
65
+ parsePath = (path) => {
66
+ const dir = dirname(path);
67
+ if (dir.trim() === "") throw new Error(`Invalid path, missing directory '${path}'`);
68
+ const base = basename(path);
69
+ if (base.trim() === "") throw new Error(`Invalid path, missing filename '${path}'`);
70
+ return {
71
+ directory: dir,
72
+ filename: base
73
+ };
74
+ };
75
+ removeFileIfExists = (path) => {
76
+ try {
77
+ fs.rmSync(path);
78
+ return true;
79
+ } catch {
80
+ return false;
81
+ }
82
+ };
83
+ };
84
+ //#endregion
85
+ export { sqlduckDefaultLogtapeLogger as n, flowbladeLogtapeSqlduckConfig as r, FileSystemUtils as t };
@@ -0,0 +1,34 @@
1
+ import { Logger } from "@logtape/logtape";
2
+
3
+ //#region src/filesystem/file-system-utils.d.ts
4
+ declare class FileSystemUtils {
5
+ #private;
6
+ constructor(params?: {
7
+ logger?: Logger;
8
+ });
9
+ /**
10
+ * Create a directory recursively if it doesn't exist
11
+ *
12
+ * @throws Error if it can't be created
13
+ */
14
+ createDirectory: (path: string) => void;
15
+ /**
16
+ * Create a directory recursively if it doesn't exist and ensure it's writable
17
+ */
18
+ createAndEnsureWritableDirectory: (label: string, path: string | undefined) => void;
19
+ /**
20
+ * Check if a path is a regular file and exists
21
+ */
22
+ isFile: (path: string) => boolean;
23
+ /**
24
+ * Check if a path is a directory and exists
25
+ */
26
+ isDirectory: (path: string) => boolean;
27
+ parsePath: (path: string) => {
28
+ directory: string;
29
+ filename: string;
30
+ };
31
+ removeFileIfExists: (path: string) => boolean;
32
+ }
33
+ //#endregion
34
+ export { FileSystemUtils };
@@ -0,0 +1,2 @@
1
+ import { t as FileSystemUtils } from "../file-system-utils-JhZ8_Dh7.mjs";
2
+ export { FileSystemUtils };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as DuckConnectionParams } from "./types-BIe-pGY0.mjs";
1
+ import { n as DuckConnectionParams } from "./types-C6EIVpFJ.mjs";
2
2
  import { DuckDBConnection, DuckDBType } from "@duckdb/node-api";
3
3
  import * as _$_logtape_logtape0 from "@logtape/logtape";
4
4
  import { Logger } from "@logtape/logtape";
@@ -266,6 +266,20 @@ declare class Database {
266
266
  declare const duckReservedKeywords: readonly ["ALL", "ANALYSE", "ANALYZE", "AND", "ANY", "ARRAY", "AS", "ASC", "ASYMMETRIC", "BOTH", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_CATALOG", "CURRENT_DATE", "CURRENT_ROLE", "CURRENT_SCHEMA", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "DEFAULT", "DEFERRABLE", "DESC", "DISTINCT", "DO", "ELSE", "END", "EXCEPT", "EXISTS", "EXTRACT", "FALSE", "FETCH", "FOR", "FOREIGN", "FROM", "GRANT", "GROUP", "HAVING", "IF", "ILIKE", "IN", "INITIALLY", "INNER", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "LATERAL", "LEADING", "LEFT", "LIKE", "LIMIT", "LOCALTIME", "LOCALTIMESTAMP", "NATURAL", "NOT", "NOTNULL", "NULL", "OFFSET", "ON", "ONLY", "OR", "ORDER", "OUTER", "OVERLAPS", "PLACING", "PRIMARY", "REFERENCES", "RETURNING", "RIGHT", "ROW", "SELECT", "SESSION_USER", "SIMILAR", "SOME", "SYMMETRIC", "TABLE", "THEN", "TO", "TRAILING", "TRUE", "UNION", "UNIQUE", "USING", "VARIADIC", "VERBOSE", "WHEN", "WHERE", "WINDOW", "WITH"];
267
267
  type DuckdbReservedKeywords = (typeof duckReservedKeywords)[number];
268
268
  //#endregion
269
+ //#region src/validation/zod/manager/duck-database-manager-zod-schemas.d.ts
270
+ declare const duckDatabaseManagerZodSchemas: {
271
+ getDatabases: z.ZodObject<{
272
+ comment: z.ZodNullable<z.ZodString>;
273
+ database_name: z.ZodString;
274
+ database_oid: z.ZodBigInt;
275
+ encrypted: z.ZodBoolean;
276
+ internal: z.ZodBoolean;
277
+ path: z.ZodNullable<z.ZodString>;
278
+ readonly: z.ZodBoolean;
279
+ type: z.ZodString;
280
+ }, z.core.$strict>;
281
+ };
282
+ //#endregion
269
283
  //#region src/manager/database/commands/duck-database-attach-command.d.ts
270
284
  type Behaviour = 'OR REPLACE' | 'IF NOT EXISTS';
271
285
  type DuckDatabaseAttachCommandOptions = {
@@ -273,6 +287,7 @@ type DuckDatabaseAttachCommandOptions = {
273
287
  };
274
288
  //#endregion
275
289
  //#region src/manager/database/duck-database-manager.d.ts
290
+ type GetDatabaseInfo = z.infer<typeof duckDatabaseManagerZodSchemas.getDatabases>;
276
291
  declare class DuckDatabaseManager {
277
292
  #private;
278
293
  readonly className = "DuckDatabaseManager";
@@ -301,27 +316,26 @@ declare class DuckDatabaseManager {
301
316
  * Check whether a specific database name / alias is currently attached
302
317
  */
303
318
  isAttached: (dbAlias: string) => Promise<boolean>;
319
+ getDatabaseByName: (dbName: string) => Promise<GetDatabaseInfo | null>;
320
+ getDatabasesByPath: (path: string) => Promise<GetDatabaseInfo | null>;
304
321
  /**
305
322
  * Return information about attached databases
306
323
  */
307
324
  getDatabases: (params?: {
308
325
  includeInternal?: boolean;
309
- }) => Promise<{
310
- database_name: string;
311
- database_oid: string;
312
- path: string | null;
313
- comment: string | null;
314
- type: string;
315
- readonly: boolean;
316
- internal: boolean;
317
- encrypted: boolean;
318
- }[]>;
326
+ }) => Promise<GetDatabaseInfo[]>;
319
327
  /**
320
328
  * Get the currently attached database names
321
329
  */
322
330
  showDatabases: () => Promise<Record<string, unknown>[]>;
331
+ /**
332
+ * @throws Error if the database isn't attached
333
+ */
323
334
  detach: (dbAlias: string) => Promise<boolean>;
324
- detachIfExists: (dbAlias: string) => Promise<boolean>;
335
+ /**
336
+ * @todo DETACH IF EXISTS is not supported in DuckDB as of v1.5.3
337
+ */
338
+ detachOrIgnore: (dbAlias: string) => Promise<boolean>;
325
339
  /**
326
340
  * The statistics recomputed by the ANALYZE statement are only used for join order optimization.
327
341
  *
package/dist/index.mjs CHANGED
@@ -1,9 +1,7 @@
1
- import { t as duckReservedKeywords } from "./duck-reserved-keywords-B00l2sbi.mjs";
2
- import { c as duckValidatorsZod, r as assertValidAliasName, s as duckConnectionParamsZodSchema } from "./zod-BXgMoCLM.mjs";
1
+ import { n as sqlduckDefaultLogtapeLogger, r as flowbladeLogtapeSqlduckConfig, t as FileSystemUtils } from "./file-system-utils-JhZ8_Dh7.mjs";
2
+ import { t as duckReservedKeywords } from "./duck-reserved-keywords-D_yi_PVW.mjs";
3
+ import { c as duckValidatorsZod, r as assertValidAliasName, s as duckConnectionParamsZodSchema } from "./zod-uQPzaK64.mjs";
3
4
  import { BIGINT, BOOLEAN, DOUBLE, DuckDBDataChunk, DuckDBInstanceCache, DuckDBTimestampMillisecondsValue, DuckDBTypeId, ENUM, FLOAT, HUGEINT, INTEGER, SMALLINT, TIMESTAMP, TIMESTAMP_MS, TINYINT, UBIGINT, UHUGEINT, UINTEGER, USMALLINT, UTINYINT, UUID, VARCHAR } from "@duckdb/node-api";
4
- import { getLogger } from "@logtape/logtape";
5
- import fs from "node:fs";
6
- import { basename, dirname } from "node:path";
7
5
  import * as z from "zod";
8
6
  import { assertNever } from "@httpx/assert";
9
7
  import { isPlainObject } from "@httpx/plain-object";
@@ -230,87 +228,6 @@ const createDuckColumnConverters = (duckTypes) => {
230
228
  return convMap;
231
229
  };
232
230
  //#endregion
233
- //#region src/config/flowblade-logtape-sqlduck.config.ts
234
- const flowbladeLogtapeSqlduckConfig = { categories: ["flowblade", "sqlduck"] };
235
- //#endregion
236
- //#region src/logger/sqlduck-default-logtape-logger.ts
237
- const sqlduckDefaultLogtapeLogger = getLogger(flowbladeLogtapeSqlduckConfig.categories);
238
- //#endregion
239
- //#region src/filesystem/file-system-utils.ts
240
- var FileSystemUtils = class {
241
- #logger;
242
- constructor(params) {
243
- this.#logger = params?.logger ?? sqlduckDefaultLogtapeLogger.with({ source: "FileSystemUtils" });
244
- }
245
- /**
246
- * Create a directory recursively if it doesn't exist
247
- *
248
- * @throws Error if it can't be created
249
- */
250
- createDirectory = (path) => {
251
- try {
252
- fs.mkdirSync(path, { recursive: true });
253
- } catch (err) {
254
- if (err.code !== "EEXIST") throw err;
255
- }
256
- };
257
- /**
258
- * Create a directory recursively if it doesn't exist and ensure it's writable
259
- */
260
- createAndEnsureWritableDirectory = (label, path) => {
261
- if (path === void 0) return;
262
- if (!fs.existsSync(path)) try {
263
- this.createDirectory(path);
264
- } catch (e) {
265
- throw new Error(`Failed to create ${label} '${path}' - ${e?.message ?? ""}`);
266
- }
267
- if (!fs.statSync(path).isDirectory()) throw new Error(`${label} '${path}' must be a directory`);
268
- try {
269
- fs.accessSync(path, fs.constants.W_OK);
270
- } catch {
271
- throw new Error(`${label} '${path}' must be writable`);
272
- }
273
- };
274
- /**
275
- * Check if a path is a regular file and exists
276
- */
277
- isFile = (path) => {
278
- try {
279
- return fs.statSync(path).isFile();
280
- } catch {
281
- return false;
282
- }
283
- };
284
- /**
285
- * Check if a path is a directory and exists
286
- */
287
- isDirectory = (path) => {
288
- try {
289
- return fs.statSync(path).isDirectory();
290
- } catch {
291
- return false;
292
- }
293
- };
294
- parsePath = (path) => {
295
- const dir = dirname(path);
296
- if (dir.trim() === "") throw new Error(`Invalid path, missing directory '${path}'`);
297
- const base = basename(path);
298
- if (base.trim() === "") throw new Error(`Invalid path, missing filename '${path}'`);
299
- return {
300
- directory: dir,
301
- filename: base
302
- };
303
- };
304
- removeFileIfExists = (path) => {
305
- try {
306
- fs.rmSync(path);
307
- return true;
308
- } catch {
309
- return false;
310
- }
311
- };
312
- };
313
- //#endregion
314
231
  //#region src/objects/database.ts
315
232
  var Database = class {
316
233
  #params;
@@ -331,6 +248,13 @@ var Database = class {
331
248
  }
332
249
  };
333
250
  //#endregion
251
+ //#region src/utils/quote-value.ts
252
+ const quoteValue = (value) => {
253
+ if (value === null || value === void 0) return "NULL";
254
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
255
+ return `'${value.replaceAll("'", "''")}'`;
256
+ };
257
+ //#endregion
334
258
  //#region src/manager/core/manager-query-executor.ts
335
259
  var ManagerQueryExecutor = class {
336
260
  #conn;
@@ -475,13 +399,42 @@ var DuckDatabaseManager = class {
475
399
  WHERE database_name = '${dbAlias}'
476
400
  ) AS is_attached;`))[0]?.is_attached ?? false;
477
401
  };
402
+ getDatabaseByName = async (dbName) => {
403
+ assertValidAliasName(dbName);
404
+ const result = await this.#executor.getRowObjectsJS("getDatabaseByName", `select database_name,
405
+ database_oid,
406
+ path,
407
+ comment,
408
+ type,
409
+ readonly,
410
+ internal,
411
+ encrypted
412
+ from duckdb_databases()
413
+ where database_name = '${dbName}'`);
414
+ if (result.length === 1) return result[0];
415
+ return null;
416
+ };
417
+ getDatabasesByPath = async (path) => {
418
+ const result = await this.#executor.getRowObjectsJS("getDatabaseByPath", `select database_name,
419
+ database_oid,
420
+ path,
421
+ comment,
422
+ type,
423
+ readonly,
424
+ internal,
425
+ encrypted
426
+ from duckdb_databases()
427
+ where path = ${quoteValue(path)}`);
428
+ if (result.length === 1) return result[0];
429
+ return null;
430
+ };
478
431
  /**
479
432
  * Return information about attached databases
480
433
  */
481
434
  getDatabases = async (params) => {
482
435
  const { includeInternal = false } = params ?? {};
483
436
  const internalFilter = includeInternal ? "1=1" : "internal = false";
484
- return this.#executor.getRowObjectsJS("listDatabases", `select database_name,
437
+ return this.#executor.getRowObjectsJS("getDatabases", `select database_name,
485
438
  database_oid,
486
439
  path,
487
440
  comment,
@@ -498,14 +451,24 @@ var DuckDatabaseManager = class {
498
451
  showDatabases = async () => {
499
452
  return await this.#executor.getRowObjectsJS("showDatabases()", `SHOW DATABASES`);
500
453
  };
454
+ /**
455
+ * @throws Error if the database isn't attached
456
+ */
501
457
  detach = async (dbAlias) => {
502
458
  assertValidAliasName(dbAlias);
503
459
  await this.#executor.getRowObjectsJS(`detach(${dbAlias})`, `DETACH ${dbAlias}`);
504
460
  return true;
505
461
  };
506
- detachIfExists = async (dbAlias) => {
462
+ /**
463
+ * @todo DETACH IF EXISTS is not supported in DuckDB as of v1.5.3
464
+ */
465
+ detachOrIgnore = async (dbAlias) => {
507
466
  assertValidAliasName(dbAlias);
508
- await this.#executor.getRowObjectsJS(`detachIfExists(${dbAlias})`, `DETACH IF EXISTS ${dbAlias}`);
467
+ try {
468
+ await this.detach(dbAlias);
469
+ } catch {
470
+ return false;
471
+ }
509
472
  return true;
510
473
  };
511
474
  /**
@@ -1,4 +1,4 @@
1
- import { a as duckStorageVersionRegexp, i as duckIdentifierNameRegex, n as duckdbReservedKeywordsSet, r as duckConnectionsOptions } from "../../duck-reserved-keywords-B00l2sbi.mjs";
1
+ import { a as duckStorageVersionRegexp, i as duckIdentifierNameRegex, n as duckdbReservedKeywordsSet, r as duckConnectionsOptions } from "../../duck-reserved-keywords-D_yi_PVW.mjs";
2
2
  import isSafeFilename from "is-safe-filename";
3
3
  import { parseDsn, parseDsnOrThrow } from "@httpx/dsn-parser";
4
4
  import * as v from "valibot";
@@ -1,4 +1,4 @@
1
- import { a as duckAllConnectionOptionsZodSchema, i as DuckTableName, o as duckConnectionParamsZodSchema, r as DuckSchemaName, t as DuckAliasName } from "../../types-BIe-pGY0.mjs";
1
+ import { a as duckAllConnectionOptionsZodSchema, i as DuckTableName, o as duckConnectionParamsZodSchema, r as DuckSchemaName, t as DuckAliasName } from "../../types-C6EIVpFJ.mjs";
2
2
  import * as z from "zod";
3
3
 
4
4
  //#region src/validation/zod/duck-asserts-zod.d.ts
@@ -1,2 +1,2 @@
1
- import { a as assertValidTableName, c as duckValidatorsZod, i as assertValidSchemaName, n as duckDsnZodSchema, o as duckAllConnectionOptionsZodSchema, r as assertValidAliasName, s as duckConnectionParamsZodSchema, t as ensureZodTableSchema } from "../../zod-BXgMoCLM.mjs";
1
+ import { a as assertValidTableName, c as duckValidatorsZod, i as assertValidSchemaName, n as duckDsnZodSchema, o as duckAllConnectionOptionsZodSchema, r as assertValidAliasName, s as duckConnectionParamsZodSchema, t as ensureZodTableSchema } from "../../zod-uQPzaK64.mjs";
2
2
  export { assertValidAliasName, assertValidSchemaName, assertValidTableName, duckAllConnectionOptionsZodSchema, duckConnectionParamsZodSchema, duckDsnZodSchema, duckValidatorsZod, ensureZodTableSchema };
@@ -1,4 +1,4 @@
1
- import { a as duckStorageVersionRegexp, i as duckIdentifierNameRegex, n as duckdbReservedKeywordsSet, r as duckConnectionsOptions } from "./duck-reserved-keywords-B00l2sbi.mjs";
1
+ import { a as duckStorageVersionRegexp, i as duckIdentifierNameRegex, n as duckdbReservedKeywordsSet, r as duckConnectionsOptions } from "./duck-reserved-keywords-D_yi_PVW.mjs";
2
2
  import isSafeFilename from "is-safe-filename";
3
3
  import * as z from "zod";
4
4
  import { parseDsn } from "@httpx/dsn-parser";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowblade/sqlduck",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -8,6 +8,10 @@
8
8
  "types": "./dist/index.d.mts",
9
9
  "default": "./dist/index.mjs"
10
10
  },
11
+ "./filesystem": {
12
+ "types": "./dist/filesystem/index.d.mts",
13
+ "default": "./dist/filesystem/index.mjs"
14
+ },
11
15
  "./zod": {
12
16
  "types": "./dist/validation/zod/index.d.mts",
13
17
  "default": "./dist/validation/zod/index.mjs"
@@ -44,6 +48,7 @@
44
48
  "test": "vitest run",
45
49
  "test-unit": "vitest run",
46
50
  "test-unit-bun": "bun --bun run vitest run",
51
+ "test-unit-deno": "deno run test-unit",
47
52
  "test-unit-watch": "vitest --ui",
48
53
  "test-e2e": "vitest -c vitest.e2e.config.ts run",
49
54
  "test-e2e-bun": "bun --bun run vitest -c vitest.e2e.config.ts run",
@@ -79,7 +84,7 @@
79
84
  },
80
85
  "devDependencies": {
81
86
  "@belgattitude/eslint-config-bases": "8.15.0",
82
- "@dotenvx/dotenvx": "1.67.0",
87
+ "@dotenvx/dotenvx": "1.68.1",
83
88
  "@duckdb/node-api": "1.5.3-r.2",
84
89
  "@faker-js/faker": "10.4.0",
85
90
  "@flowblade/source-kysely": "^1.4.1",
@@ -92,7 +97,7 @@
92
97
  "@types/node": "25.9.1",
93
98
  "@typescript-eslint/eslint-plugin": "8.60.0",
94
99
  "@typescript-eslint/parser": "8.60.0",
95
- "@typescript/native-preview": "7.0.0-dev.20260524.1",
100
+ "@typescript/native-preview": "7.0.0-dev.20260525.1",
96
101
  "@vitest/coverage-v8": "4.1.7",
97
102
  "@vitest/ui": "4.1.7",
98
103
  "ansis": "4.3.0",
@@ -100,7 +105,7 @@
100
105
  "core-js": "3.49.0",
101
106
  "cross-env": "10.1.0",
102
107
  "es-check": "9.6.4",
103
- "es-toolkit": "1.46.1",
108
+ "es-toolkit": "1.47.0",
104
109
  "esbuild": "0.28.0",
105
110
  "eslint": "8.57.1",
106
111
  "execa": "9.6.1",