@flowblade/sqlduck 0.13.0 → 0.15.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.
package/README.md CHANGED
@@ -29,7 +29,7 @@ import { conn } from "./db.config.ts";
29
29
 
30
30
  const dbManager = new DuckDatabaseManager(conn);
31
31
  const database = await dbManager.attach({
32
- type: 'memory', // can be 'duckdb', ...
32
+ type: 'memory', // can be 'filesystem', ...
33
33
  alias: 'mydb',
34
34
  options: { COMPRESS: 'false' },
35
35
  });
@@ -0,0 +1,126 @@
1
+ //#region src/validation/core/base-validators.ts
2
+ const duckIdentifierNameRegex = /^[a-z_]\w*$/i;
3
+ const duckStorageVersionRegexp = /^v?\d{1,4}\.\d{1,4}\.\d{1,4}$/;
4
+ //#endregion
5
+ //#region src/validation/core/duck-connections-options.ts
6
+ const duckConnectionsOptions = {
7
+ types: [
8
+ "DUCKDB",
9
+ "SQLITE",
10
+ "MYSQL",
11
+ "PostgreSQL"
12
+ ],
13
+ accessModes: ["READ_ONLY", "READ_WRITE"],
14
+ encryptionCiphers: [
15
+ "CBC",
16
+ "CTR",
17
+ "GCM"
18
+ ]
19
+ };
20
+ //#endregion
21
+ //#region src/validation/core/duck-reserved-keywords.ts
22
+ /**
23
+ * DuckDB reserved keywords that cannot be used as unquoted identifiers.
24
+ * @see https://duckdb.org/docs/sql/keywords-and-identifiers.html
25
+ */
26
+ const duckReservedKeywords = [
27
+ "ALL",
28
+ "ANALYSE",
29
+ "ANALYZE",
30
+ "AND",
31
+ "ANY",
32
+ "ARRAY",
33
+ "AS",
34
+ "ASC",
35
+ "ASYMMETRIC",
36
+ "BOTH",
37
+ "CASE",
38
+ "CAST",
39
+ "CHECK",
40
+ "COLLATE",
41
+ "COLUMN",
42
+ "CONSTRAINT",
43
+ "CREATE",
44
+ "CROSS",
45
+ "CURRENT_CATALOG",
46
+ "CURRENT_DATE",
47
+ "CURRENT_ROLE",
48
+ "CURRENT_SCHEMA",
49
+ "CURRENT_TIME",
50
+ "CURRENT_TIMESTAMP",
51
+ "CURRENT_USER",
52
+ "DEFAULT",
53
+ "DEFERRABLE",
54
+ "DESC",
55
+ "DISTINCT",
56
+ "DO",
57
+ "ELSE",
58
+ "END",
59
+ "EXCEPT",
60
+ "EXISTS",
61
+ "EXTRACT",
62
+ "FALSE",
63
+ "FETCH",
64
+ "FOR",
65
+ "FOREIGN",
66
+ "FROM",
67
+ "GRANT",
68
+ "GROUP",
69
+ "HAVING",
70
+ "IF",
71
+ "ILIKE",
72
+ "IN",
73
+ "INITIALLY",
74
+ "INNER",
75
+ "INTERSECT",
76
+ "INTO",
77
+ "IS",
78
+ "ISNULL",
79
+ "JOIN",
80
+ "LATERAL",
81
+ "LEADING",
82
+ "LEFT",
83
+ "LIKE",
84
+ "LIMIT",
85
+ "LOCALTIME",
86
+ "LOCALTIMESTAMP",
87
+ "NATURAL",
88
+ "NOT",
89
+ "NOTNULL",
90
+ "NULL",
91
+ "OFFSET",
92
+ "ON",
93
+ "ONLY",
94
+ "OR",
95
+ "ORDER",
96
+ "OUTER",
97
+ "OVERLAPS",
98
+ "PLACING",
99
+ "PRIMARY",
100
+ "REFERENCES",
101
+ "RETURNING",
102
+ "RIGHT",
103
+ "ROW",
104
+ "SELECT",
105
+ "SESSION_USER",
106
+ "SIMILAR",
107
+ "SOME",
108
+ "SYMMETRIC",
109
+ "TABLE",
110
+ "THEN",
111
+ "TO",
112
+ "TRAILING",
113
+ "TRUE",
114
+ "UNION",
115
+ "UNIQUE",
116
+ "USING",
117
+ "VARIADIC",
118
+ "VERBOSE",
119
+ "WHEN",
120
+ "WHERE",
121
+ "WINDOW",
122
+ "WITH"
123
+ ];
124
+ const duckdbReservedKeywordsSet = new Set(duckReservedKeywords);
125
+ //#endregion
126
+ export { duckStorageVersionRegexp as a, duckIdentifierNameRegex as i, duckdbReservedKeywordsSet as n, duckConnectionsOptions as r, duckReservedKeywords as t };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as DuckConnectionParams } from "./types-DCqYqEsa.mjs";
1
+ import { n as DuckConnectionParams } from "./types-dnhcognF.mjs";
2
2
  import * as _$_duckdb_node_api0 from "@duckdb/node-api";
3
3
  import { DuckDBConnection, DuckDBType } from "@duckdb/node-api";
4
4
  import * as _$_logtape_logtape0 from "@logtape/logtape";
@@ -143,6 +143,13 @@ type ToTableParams<TSchema extends TableSchemaZod> = {
143
143
  * @default true
144
144
  */
145
145
  autoCheckpoint?: boolean;
146
+ /**
147
+ * Checkpoint the table after 'n' chunks have been appended
148
+ *
149
+ * For example if the chunkSize is 2048, setting frequency to 2
150
+ * will checkpoint the table every 4096 rows (2x chunksize)
151
+ */
152
+ checkpointChunksFrequency?: number;
146
153
  };
147
154
  type ToTableResult = {
148
155
  /**
@@ -251,7 +258,7 @@ declare class DuckDatabaseManager {
251
258
  * ```typescript
252
259
  * const dbManager = new DuckDatabaseManager(conn);
253
260
  * const database = dbManager.attach({
254
- * type: 'memory', // can be 'duckdb', 's3'...
261
+ * type: 'memory', // can be 'filesystem'...
255
262
  * alias: 'mydb',
256
263
  * options: { COMPRESS: 'true' }
257
264
  * });
@@ -276,6 +283,15 @@ declare class DuckDatabaseManager {
276
283
  analyze: () => Promise<boolean>;
277
284
  checkpoint: (dbAlias: string) => Promise<boolean>;
278
285
  vacuum: () => Promise<boolean>;
286
+ /**
287
+ * Helper to create an initial database file.
288
+ */
289
+ createDatabaseFile: (params: {
290
+ path: string;
291
+ createDirectory?: boolean;
292
+ }) => Promise<{
293
+ status: "exists" | "created";
294
+ }>;
279
295
  }
280
296
  //#endregion
281
297
  //#region src/config/flowblade-logtape-sqlduck.config.d.ts
package/dist/index.mjs CHANGED
@@ -1,6 +1,9 @@
1
- import { c as duckValidatorsZod, l as duckReservedKeywords, r as assertValidAliasName, s as duckConnectionParamsZodSchema } from "./zod-DcIc8xQC.mjs";
2
- import { BIGINT, BOOLEAN, DOUBLE, DuckDBDataChunk, DuckDBTimestampValue, FLOAT, HUGEINT, INTEGER, SMALLINT, TIMESTAMP, TINYINT, UBIGINT, UHUGEINT, UINTEGER, USMALLINT, UTINYINT, UUID, VARCHAR } from "@duckdb/node-api";
1
+ import { t as duckReservedKeywords } from "./duck-reserved-keywords-B8XUjnaY.mjs";
2
+ import { n as assertValidAliasName, o as duckConnectionParamsZodSchema, s as duckValidatorsZod } from "./zod-CuPjTLv8.mjs";
3
+ import { BIGINT, BOOLEAN, DOUBLE, DuckDBDataChunk, DuckDBInstanceCache, DuckDBTimestampValue, FLOAT, HUGEINT, INTEGER, SMALLINT, TIMESTAMP, TINYINT, UBIGINT, UHUGEINT, UINTEGER, USMALLINT, UTINYINT, UUID, VARCHAR } from "@duckdb/node-api";
3
4
  import { getLogger } from "@logtape/logtape";
5
+ import fs from "node:fs";
6
+ import { basename, dirname } from "node:path";
4
7
  import * as z from "zod";
5
8
  import { assertNever } from "@httpx/assert";
6
9
  import { isPlainObject } from "@httpx/plain-object";
@@ -127,6 +130,81 @@ const flowbladeLogtapeSqlduckConfig = { categories: ["flowblade", "sqlduck"] };
127
130
  //#region src/logger/sqlduck-default-logtape-logger.ts
128
131
  const sqlduckDefaultLogtapeLogger = getLogger(flowbladeLogtapeSqlduckConfig.categories);
129
132
  //#endregion
133
+ //#region src/filesystem/file-system-utils.ts
134
+ var FileSystemUtils = class {
135
+ #logger;
136
+ constructor(params) {
137
+ this.#logger = params?.logger ?? sqlduckDefaultLogtapeLogger.with({ source: "FileSystemUtils" });
138
+ }
139
+ /**
140
+ * Create a directory recursively if it doesn't exist
141
+ *
142
+ * @throws Error if it can't be created
143
+ */
144
+ createDirectory = (path) => {
145
+ try {
146
+ fs.mkdirSync(path, { recursive: true });
147
+ } catch (err) {
148
+ if (err.code !== "EEXIST") throw err;
149
+ }
150
+ };
151
+ /**
152
+ * Create a directory recursively if it doesn't exist and ensure it's writable
153
+ */
154
+ createAndEnsureWritableDirectory = (label, path) => {
155
+ if (path === void 0) return;
156
+ if (!fs.existsSync(path)) try {
157
+ this.createDirectory(path);
158
+ } catch (e) {
159
+ throw new Error(`Failed to create ${label} '${path}' - ${e?.message ?? ""}`);
160
+ }
161
+ if (!fs.statSync(path).isDirectory()) throw new Error(`${label} '${path}' must be a directory`);
162
+ try {
163
+ fs.accessSync(path, fs.constants.W_OK);
164
+ } catch {
165
+ throw new Error(`${label} '${path}' must be writable`);
166
+ }
167
+ };
168
+ /**
169
+ * Check if a path is a regular file and exists
170
+ */
171
+ isFile = (path) => {
172
+ try {
173
+ return fs.statSync(path).isFile();
174
+ } catch {
175
+ return false;
176
+ }
177
+ };
178
+ /**
179
+ * Check if a path is a directory and exists
180
+ */
181
+ isDirectory = (path) => {
182
+ try {
183
+ return fs.statSync(path).isDirectory();
184
+ } catch {
185
+ return false;
186
+ }
187
+ };
188
+ parsePath = (path) => {
189
+ const dir = dirname(path);
190
+ if (dir.trim() === "") throw new Error(`Invalid path, missing directory '${path}'`);
191
+ const base = basename(path);
192
+ if (base.trim() === "") throw new Error(`Invalid path, missing filename '${path}'`);
193
+ return {
194
+ directory: dir,
195
+ filename: base
196
+ };
197
+ };
198
+ removeFileIfExists = (path) => {
199
+ try {
200
+ fs.rmSync(path);
201
+ return true;
202
+ } catch {
203
+ return false;
204
+ }
205
+ };
206
+ };
207
+ //#endregion
130
208
  //#region src/objects/database.ts
131
209
  var Database = class {
132
210
  #params;
@@ -163,7 +241,7 @@ var DuckDatabaseAttachCommand = class {
163
241
  case "memory":
164
242
  parts.push("':memory:'");
165
243
  break;
166
- case "duckdb":
244
+ case "filesystem":
167
245
  parts.push(`'${dbParams.path}'`);
168
246
  break;
169
247
  default: assertNever(type);
@@ -206,6 +284,7 @@ var DuckDatabaseAttachCommand = class {
206
284
  var DuckDatabaseManager = class {
207
285
  #conn;
208
286
  #logger;
287
+ #fs;
209
288
  constructor(conn, params) {
210
289
  this.#conn = conn;
211
290
  this.#logger = params?.logger ?? sqlduckDefaultLogtapeLogger.with({ source: "DuckDatabaseManager" });
@@ -217,7 +296,7 @@ var DuckDatabaseManager = class {
217
296
  * ```typescript
218
297
  * const dbManager = new DuckDatabaseManager(conn);
219
298
  * const database = dbManager.attach({
220
- * type: 'memory', // can be 'duckdb', 's3'...
299
+ * type: 'memory', // can be 'filesystem'...
221
300
  * alias: 'mydb',
222
301
  * options: { COMPRESS: 'true' }
223
302
  * });
@@ -228,7 +307,12 @@ var DuckDatabaseManager = class {
228
307
  attach = async (dbParams, options) => {
229
308
  const params = duckConnectionParamsZodSchema.parse(dbParams);
230
309
  const rawSql = new DuckDatabaseAttachCommand(params, options).getRawSql();
231
- await this.#executeRawSqlCommand(`attach(${params.alias})`, rawSql);
310
+ const { behaviour } = options ?? {};
311
+ await this.#executeRawSqlCommand([
312
+ `attach(${params.alias}`,
313
+ behaviour ?? null,
314
+ ")"
315
+ ].filter(Boolean).join(","), rawSql);
232
316
  return new Database({ alias: params.alias });
233
317
  };
234
318
  attachOrReplace = async (dbParams) => {
@@ -271,13 +355,39 @@ var DuckDatabaseManager = class {
271
355
  await this.#executeRawSqlCommand("vacuum()", "VACUUM");
272
356
  return true;
273
357
  };
358
+ /**
359
+ * Helper to create an initial database file.
360
+ */
361
+ createDatabaseFile = async (params) => {
362
+ const startTime = Date.now();
363
+ const { path, createDirectory = true } = params;
364
+ const fs = this.#getFs();
365
+ if (fs.isFile(path)) return { status: "exists" };
366
+ if (createDirectory) {
367
+ const { directory } = fs.parsePath(path);
368
+ fs.createAndEnsureWritableDirectory("database file directory", directory);
369
+ }
370
+ const instanceCache = new DuckDBInstanceCache();
371
+ try {
372
+ (await (await instanceCache.getOrCreateInstance(path)).connect()).closeSync();
373
+ const timeMs = Math.round(Date.now() - startTime);
374
+ this.#logger.info(`DuckDatabaseManager.createDatabaseFile('${path}') in ${timeMs}ms`, {
375
+ timeMs,
376
+ path
377
+ });
378
+ } catch (e) {
379
+ this.#logger.error(`DuckDatabaseManager.createDatabaseFile('${path}') failed - ${e?.message ?? ""}`, { path });
380
+ throw e;
381
+ }
382
+ return { status: "created" };
383
+ };
274
384
  #executeRawSqlCommand = async (name, rawSql) => {
275
385
  const startTime = Date.now();
276
386
  try {
277
387
  const result = await this.#conn.runAndReadAll(rawSql);
278
388
  const timeMs = Math.round(Date.now() - startTime);
279
389
  const data = result.getRowObjectsJS();
280
- this.#logger.info(`DuckDatabaseManager.${name} in ${timeMs}ms`, { timeMs });
390
+ this.#logger.debug(`DuckDatabaseManager.${name} in ${timeMs}ms`, { timeMs });
281
391
  return data;
282
392
  } catch (e) {
283
393
  const msg = `DuckDatabaseManager: failed to run "${name}" - ${e?.message ?? ""}`;
@@ -290,6 +400,10 @@ var DuckDatabaseManager = class {
290
400
  throw new Error(msg, { cause: e });
291
401
  }
292
402
  };
403
+ #getFs = () => {
404
+ if (this.#fs === void 0) this.#fs = new FileSystemUtils({ logger: this.#logger });
405
+ return this.#fs;
406
+ };
293
407
  };
294
408
  //#endregion
295
409
  //#region src/table/get-duckdb-number-column-type.ts
@@ -514,9 +628,11 @@ var SqlDuck = class {
514
628
  * ```
515
629
  */
516
630
  toTable = async (params) => {
517
- const { table, schema, chunkSize = 2048, rowStream, createOptions, onDataAppended, autoCheckpoint = true } = params;
631
+ const { table, schema, chunkSize = 2048, rowStream, createOptions, onDataAppended, autoCheckpoint = true, checkpointChunksFrequency = 10 } = params;
518
632
  if (!Number.isSafeInteger(chunkSize) || chunkSize < 1 || chunkSize > 2048) throw new Error("chunkSize must be a number between 1 and 2048");
519
633
  if (autoCheckpoint && typeof table.databaseName !== "string") throw new Error("autoCheckpoint requires table.databaseName to be provided.");
634
+ if (checkpointChunksFrequency && typeof table.databaseName !== "string") throw new Error("checkpointChunksFrequency requires table.databaseName to be provided.");
635
+ if (checkpointChunksFrequency !== void 0 && checkpointChunksFrequency < 1) throw new Error("checkpointChunksFrequency must be a positive number.");
520
636
  const dbManager = new DuckDatabaseManager(this.#conn);
521
637
  const timeStart = Date.now();
522
638
  const { columnTypes, ddl } = await createTableFromZod({
@@ -533,6 +649,7 @@ var SqlDuck = class {
533
649
  rows: rowStream,
534
650
  chunkSize
535
651
  });
652
+ let appendedChunkCount = 0;
536
653
  try {
537
654
  for await (const dataChunk of columnStream) {
538
655
  const chunk = DuckDBDataChunk.create(chunkTypes);
@@ -541,12 +658,13 @@ var SqlDuck = class {
541
658
  chunk.setColumns(dataChunk);
542
659
  appender.appendDataChunk(chunk);
543
660
  appender.flushSync();
661
+ appendedChunkCount += 1;
544
662
  if (onDataAppended !== void 0) {
545
663
  const payload = dataAppendedCollector(totalRows);
546
664
  if (isOnDataAppendedAsyncCb(onDataAppended)) await onDataAppended(payload);
547
665
  else onDataAppended(payload);
548
666
  }
549
- if (autoCheckpoint && typeof table.databaseName === "string") try {
667
+ if (checkpointChunksFrequency !== void 0 && appendedChunkCount % checkpointChunksFrequency === 0 && typeof table.databaseName === "string") try {
550
668
  await dbManager.checkpoint(table.databaseName);
551
669
  } catch (e) {
552
670
  this.#logger.warning(`Failed to checkpoint database '${table.databaseName}' after appending chunk into table '${table.getFullName()}' - ${e?.message ?? ""}`, { table: table.getFullName() });
@@ -10,6 +10,8 @@ declare const duckAllConnectionOptionsZodSchema: z.ZodObject<{
10
10
  type: z.ZodOptional<z.ZodEnum<{
11
11
  DUCKDB: "DUCKDB";
12
12
  SQLITE: "SQLITE";
13
+ MYSQL: "MYSQL";
14
+ PostgreSQL: "PostgreSQL";
13
15
  }>>;
14
16
  blockSize: z.ZodOptional<z.ZodInt32>;
15
17
  rowGroupSize: z.ZodOptional<z.ZodInt32>;
@@ -33,6 +35,8 @@ declare const duckConnectionParamsZodSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
33
35
  type: z.ZodOptional<z.ZodEnum<{
34
36
  DUCKDB: "DUCKDB";
35
37
  SQLITE: "SQLITE";
38
+ MYSQL: "MYSQL";
39
+ PostgreSQL: "PostgreSQL";
36
40
  }>>;
37
41
  blockSize: z.ZodOptional<z.ZodInt32>;
38
42
  rowGroupSize: z.ZodOptional<z.ZodInt32>;
@@ -45,7 +49,7 @@ declare const duckConnectionParamsZodSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
45
49
  }>>;
46
50
  }, z.core.$strict>>;
47
51
  }, z.core.$strict>, z.ZodObject<{
48
- type: z.ZodLiteral<"duckdb">;
52
+ type: z.ZodLiteral<"filesystem">;
49
53
  path: z.ZodString;
50
54
  alias: z.ZodString;
51
55
  options: z.ZodOptional<z.ZodObject<{
@@ -57,6 +61,8 @@ declare const duckConnectionParamsZodSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
57
61
  type: z.ZodOptional<z.ZodEnum<{
58
62
  DUCKDB: "DUCKDB";
59
63
  SQLITE: "SQLITE";
64
+ MYSQL: "MYSQL";
65
+ PostgreSQL: "PostgreSQL";
60
66
  }>>;
61
67
  blockSize: z.ZodOptional<z.ZodInt32>;
62
68
  rowGroupSize: z.ZodOptional<z.ZodInt32>;
@@ -0,0 +1,127 @@
1
+ import * as v from "valibot";
2
+
3
+ //#region src/validation/valibot/duck-connection-params-valibot-schema.d.ts
4
+ declare const duckAllConnectionOptionsValibotSchema: v.ObjectSchema<{
5
+ readonly accessMode: v.OptionalSchema<v.PicklistSchema<readonly ["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
6
+ readonly compress: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
7
+ readonly type: v.OptionalSchema<v.PicklistSchema<readonly ["DUCKDB", "SQLITE", "MYSQL", "PostgreSQL"], undefined>, undefined>;
8
+ readonly blockSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 16384, undefined>, v.MaxValueAction<number, 262144, undefined>]>, undefined>;
9
+ readonly rowGroupSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
10
+ readonly storageVersion: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.StartsWithAction<string, "v", undefined>, v.RegexAction<string, undefined>]>, undefined>;
11
+ readonly encryptionKey: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 8, undefined>]>, undefined>;
12
+ readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<readonly ["CBC", "CTR", "GCM"], undefined>, undefined>;
13
+ }, undefined>;
14
+ type DuckAllConnectionOptionsValibotSchema = v.InferOutput<typeof duckAllConnectionOptionsValibotSchema>;
15
+ declare const duckConnectionParamsValibotSchema: v.VariantSchema<"type", [v.ObjectSchema<{
16
+ readonly type: v.LiteralSchema<"memory", undefined>;
17
+ readonly alias: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 120, undefined>, v.RegexAction<string, "Identifier must start with a letter or underscore, and contain only letters, numbers and underscores">, v.CheckAction<string, "Identifier value is a DuckDB reserved keyword and cannot be used as an identifier">]>;
18
+ readonly options: v.OptionalSchema<v.ObjectSchema<{
19
+ readonly accessMode: v.OptionalSchema<v.PicklistSchema<readonly ["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
20
+ readonly compress: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
21
+ readonly type: v.OptionalSchema<v.PicklistSchema<readonly ["DUCKDB", "SQLITE", "MYSQL", "PostgreSQL"], undefined>, undefined>;
22
+ readonly blockSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 16384, undefined>, v.MaxValueAction<number, 262144, undefined>]>, undefined>;
23
+ readonly rowGroupSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
24
+ readonly storageVersion: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.StartsWithAction<string, "v", undefined>, v.RegexAction<string, undefined>]>, undefined>;
25
+ readonly encryptionKey: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 8, undefined>]>, undefined>;
26
+ readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<readonly ["CBC", "CTR", "GCM"], undefined>, undefined>;
27
+ }, undefined>, undefined>;
28
+ }, undefined>, v.ObjectSchema<{
29
+ readonly type: v.LiteralSchema<"filesystem", undefined>;
30
+ readonly path: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.CheckAction<string, "Invalid database filename - it must be a safe filename (no path traversal, no absolute paths, no reserved names, etc.)">, v.CheckAction<string, "Invalid database pathname - it must be an absolute path with no traversal">]>;
31
+ readonly alias: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 120, undefined>, v.RegexAction<string, "Identifier must start with a letter or underscore, and contain only letters, numbers and underscores">, v.CheckAction<string, "Identifier value is a DuckDB reserved keyword and cannot be used as an identifier">]>;
32
+ readonly options: v.OptionalSchema<v.ObjectSchema<{
33
+ readonly accessMode: v.OptionalSchema<v.PicklistSchema<readonly ["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
34
+ readonly compress: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
35
+ readonly type: v.OptionalSchema<v.PicklistSchema<readonly ["DUCKDB", "SQLITE", "MYSQL", "PostgreSQL"], undefined>, undefined>;
36
+ readonly blockSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 16384, undefined>, v.MaxValueAction<number, 262144, undefined>]>, undefined>;
37
+ readonly rowGroupSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
38
+ readonly storageVersion: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.StartsWithAction<string, "v", undefined>, v.RegexAction<string, undefined>]>, undefined>;
39
+ readonly encryptionKey: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 8, undefined>]>, undefined>;
40
+ readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<readonly ["CBC", "CTR", "GCM"], undefined>, undefined>;
41
+ }, undefined>, undefined>;
42
+ }, undefined>], undefined>;
43
+ //#endregion
44
+ //#region src/validation/valibot/duck-dsn-valibot-schema.d.ts
45
+ declare const duckDsnValibotSchema: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.CheckAction<string, (input: v.CheckIssue<string>) => string>, v.TransformAction<string, {
46
+ type: "memory";
47
+ alias: string;
48
+ options?: {
49
+ accessMode?: "READ_ONLY" | "READ_WRITE" | undefined;
50
+ compress?: boolean | undefined;
51
+ type?: "DUCKDB" | "SQLITE" | "MYSQL" | "PostgreSQL" | undefined;
52
+ blockSize?: number | undefined;
53
+ rowGroupSize?: number | undefined;
54
+ storageVersion?: string | undefined;
55
+ encryptionKey?: string | undefined;
56
+ encryptionCipher?: "CBC" | "CTR" | "GCM" | undefined;
57
+ } | undefined;
58
+ } | {
59
+ type: "filesystem";
60
+ path: string;
61
+ alias: string;
62
+ options?: {
63
+ accessMode?: "READ_ONLY" | "READ_WRITE" | undefined;
64
+ compress?: boolean | undefined;
65
+ type?: "DUCKDB" | "SQLITE" | "MYSQL" | "PostgreSQL" | undefined;
66
+ blockSize?: number | undefined;
67
+ rowGroupSize?: number | undefined;
68
+ storageVersion?: string | undefined;
69
+ encryptionKey?: string | undefined;
70
+ encryptionCipher?: "CBC" | "CTR" | "GCM" | undefined;
71
+ } | undefined;
72
+ }>, v.VariantSchema<"type", [v.ObjectSchema<{
73
+ readonly type: v.LiteralSchema<"memory", undefined>;
74
+ readonly alias: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 120, undefined>, v.RegexAction<string, "Identifier must start with a letter or underscore, and contain only letters, numbers and underscores">, v.CheckAction<string, "Identifier value is a DuckDB reserved keyword and cannot be used as an identifier">]>;
75
+ readonly options: v.OptionalSchema<v.ObjectSchema<{
76
+ readonly accessMode: v.OptionalSchema<v.PicklistSchema<readonly ["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
77
+ readonly compress: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
78
+ readonly type: v.OptionalSchema<v.PicklistSchema<readonly ["DUCKDB", "SQLITE", "MYSQL", "PostgreSQL"], undefined>, undefined>;
79
+ readonly blockSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 16384, undefined>, v.MaxValueAction<number, 262144, undefined>]>, undefined>;
80
+ readonly rowGroupSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
81
+ readonly storageVersion: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.StartsWithAction<string, "v", undefined>, v.RegexAction<string, undefined>]>, undefined>;
82
+ readonly encryptionKey: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 8, undefined>]>, undefined>;
83
+ readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<readonly ["CBC", "CTR", "GCM"], undefined>, undefined>;
84
+ }, undefined>, undefined>;
85
+ }, undefined>, v.ObjectSchema<{
86
+ readonly type: v.LiteralSchema<"filesystem", undefined>;
87
+ readonly path: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.CheckAction<string, "Invalid database filename - it must be a safe filename (no path traversal, no absolute paths, no reserved names, etc.)">, v.CheckAction<string, "Invalid database pathname - it must be an absolute path with no traversal">]>;
88
+ readonly alias: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 120, undefined>, v.RegexAction<string, "Identifier must start with a letter or underscore, and contain only letters, numbers and underscores">, v.CheckAction<string, "Identifier value is a DuckDB reserved keyword and cannot be used as an identifier">]>;
89
+ readonly options: v.OptionalSchema<v.ObjectSchema<{
90
+ readonly accessMode: v.OptionalSchema<v.PicklistSchema<readonly ["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
91
+ readonly compress: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
92
+ readonly type: v.OptionalSchema<v.PicklistSchema<readonly ["DUCKDB", "SQLITE", "MYSQL", "PostgreSQL"], undefined>, undefined>;
93
+ readonly blockSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 16384, undefined>, v.MaxValueAction<number, 262144, undefined>]>, undefined>;
94
+ readonly rowGroupSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
95
+ readonly storageVersion: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.StartsWithAction<string, "v", undefined>, v.RegexAction<string, undefined>]>, undefined>;
96
+ readonly encryptionKey: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 8, undefined>]>, undefined>;
97
+ readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<readonly ["CBC", "CTR", "GCM"], undefined>, undefined>;
98
+ }, undefined>, undefined>;
99
+ }, undefined>], undefined>]>;
100
+ //#endregion
101
+ //#region src/validation/valibot/duck-identifier-valibot-schema.d.ts
102
+ /**
103
+ * Check whether a table name identifier is valid
104
+ */
105
+ declare const duckIdentifierValibotSchema: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 120, undefined>, v.RegexAction<string, "Identifier must start with a letter or underscore, and contain only letters, numbers and underscores">, v.CheckAction<string, "Identifier value is a DuckDB reserved keyword and cannot be used as an identifier">]>;
106
+ //#endregion
107
+ //#region src/validation/valibot/duck-validators-valibot.d.ts
108
+ /**
109
+ * Common validators for duckdb parameters, tables...
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * import { duckValidatorsValibot } from '@flowblade/sqlduck/valibot';
114
+ *
115
+ * ```
116
+ */
117
+ declare const duckValidatorsValibot: {
118
+ /**
119
+ * Validate duckdb objects names like table, alias, and schemas
120
+ * for validity.
121
+ */
122
+ readonly aliasName: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 120, undefined>, v.RegexAction<string, "Identifier must start with a letter or underscore, and contain only letters, numbers and underscores">, v.CheckAction<string, "Identifier value is a DuckDB reserved keyword and cannot be used as an identifier">]>;
123
+ readonly schemaName: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 120, undefined>, v.RegexAction<string, "Identifier must start with a letter or underscore, and contain only letters, numbers and underscores">, v.CheckAction<string, "Identifier value is a DuckDB reserved keyword and cannot be used as an identifier">]>;
124
+ readonly tableName: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 120, undefined>, v.RegexAction<string, "Identifier must start with a letter or underscore, and contain only letters, numbers and underscores">, v.CheckAction<string, "Identifier value is a DuckDB reserved keyword and cannot be used as an identifier">]>;
125
+ };
126
+ //#endregion
127
+ export { type DuckAllConnectionOptionsValibotSchema, duckAllConnectionOptionsValibotSchema, duckConnectionParamsValibotSchema, duckDsnValibotSchema, duckIdentifierValibotSchema, duckValidatorsValibot };
@@ -0,0 +1,69 @@
1
+ import { a as duckStorageVersionRegexp, i as duckIdentifierNameRegex, n as duckdbReservedKeywordsSet, r as duckConnectionsOptions } from "../../duck-reserved-keywords-B8XUjnaY.mjs";
2
+ import isSafeFilename from "is-safe-filename";
3
+ import { parseDsn, parseDsnOrThrow } from "@httpx/dsn-parser";
4
+ import * as v from "valibot";
5
+ //#region src/validation/valibot/duck-identifier-valibot-schema.ts
6
+ /**
7
+ * Check whether a table name identifier is valid
8
+ */
9
+ const duckIdentifierValibotSchema = v.pipe(v.string(), v.minLength(1), v.maxLength(120), v.regex(duckIdentifierNameRegex, "Identifier must start with a letter or underscore, and contain only letters, numbers and underscores"), v.check((value) => !duckdbReservedKeywordsSet.has(value.toUpperCase()), "Identifier value is a DuckDB reserved keyword and cannot be used as an identifier"));
10
+ //#endregion
11
+ //#region src/validation/valibot/duck-validators-valibot.ts
12
+ /**
13
+ * Common validators for duckdb parameters, tables...
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { duckValidatorsValibot } from '@flowblade/sqlduck/valibot';
18
+ *
19
+ * ```
20
+ */
21
+ const duckValidatorsValibot = {
22
+ aliasName: duckIdentifierValibotSchema,
23
+ schemaName: duckIdentifierValibotSchema,
24
+ tableName: duckIdentifierValibotSchema
25
+ };
26
+ //#endregion
27
+ //#region src/validation/valibot/duck-connection-params-valibot-schema.ts
28
+ const duckAllConnectionOptionsValibotSchema = v.object({
29
+ accessMode: v.optional(v.picklist(duckConnectionsOptions.accessModes)),
30
+ compress: v.optional(v.boolean()),
31
+ type: v.optional(v.picklist(duckConnectionsOptions.types)),
32
+ blockSize: v.optional(v.pipe(v.number(), v.integer(), v.minValue(16384), v.maxValue(262144))),
33
+ rowGroupSize: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))),
34
+ storageVersion: v.optional(v.pipe(v.string(), v.startsWith("v"), v.regex(duckStorageVersionRegexp))),
35
+ encryptionKey: v.optional(v.pipe(v.string(), v.minLength(8))),
36
+ encryptionCipher: v.optional(v.picklist(duckConnectionsOptions.encryptionCiphers))
37
+ });
38
+ const duckConnectionParamsValibotSchema = v.variant("type", [v.object({
39
+ type: v.literal("memory"),
40
+ alias: duckValidatorsValibot.aliasName,
41
+ options: v.optional(duckAllConnectionOptionsValibotSchema)
42
+ }), v.object({
43
+ type: v.literal("filesystem"),
44
+ path: v.pipe(v.string(), v.check((path) => {
45
+ const filename = path.replace("\\", "/").split("/").at(-1);
46
+ return typeof filename === "string" && isSafeFilename(filename);
47
+ }, "Invalid database filename - it must be a safe filename (no path traversal, no absolute paths, no reserved names, etc.)"), v.check((path) => {
48
+ const pathname = "/" + path.replace("\\", "/").split("/").slice(0, -1).filter(Boolean).join("/");
49
+ return pathname.length > 0 && pathname.startsWith("/") && !pathname.includes("..");
50
+ }, "Invalid database pathname - it must be an absolute path with no traversal")),
51
+ alias: duckValidatorsValibot.aliasName,
52
+ options: v.optional(duckAllConnectionOptionsValibotSchema)
53
+ })]);
54
+ //#endregion
55
+ //#region src/validation/valibot/duck-dsn-valibot-schema.ts
56
+ const duckDsnValibotSchema = v.pipe(v.string(), v.check((dsn) => parseDsn(dsn).success, (input) => {
57
+ return parseDsn(input.input)?.message ?? "Invalid DSN";
58
+ }), v.transform((dsn) => {
59
+ const parsed = parseDsnOrThrow(dsn);
60
+ const { path, ...options } = parsed.params ?? {};
61
+ return {
62
+ type: parsed.host,
63
+ alias: parsed.db,
64
+ ...path ? { path } : {},
65
+ options: { ...options }
66
+ };
67
+ }), duckConnectionParamsValibotSchema);
68
+ //#endregion
69
+ export { duckAllConnectionOptionsValibotSchema, duckConnectionParamsValibotSchema, duckDsnValibotSchema, duckIdentifierValibotSchema, duckValidatorsValibot };
@@ -1,11 +1,92 @@
1
- import { a as duckAllConnectionOptionsZodSchema, i as DuckTableName, n as DuckConnectionParams, o as duckConnectionParamsZodSchema, r as DuckSchemaName, t as DuckAliasName } from "../../types-DCqYqEsa.mjs";
2
- import * as _$zod from "zod";
1
+ import { a as duckAllConnectionOptionsZodSchema, i as DuckTableName, o as duckConnectionParamsZodSchema, r as DuckSchemaName, t as DuckAliasName } from "../../types-dnhcognF.mjs";
2
+ import * as z from "zod";
3
3
 
4
4
  //#region src/validation/zod/duck-asserts-zod.d.ts
5
5
  declare function assertValidAliasName(aliasName: string): asserts aliasName is DuckAliasName;
6
6
  declare function assertValidSchemaName(schemaName: string): asserts schemaName is DuckSchemaName;
7
7
  declare function assertValidTableName(tableName: string): asserts tableName is DuckTableName;
8
8
  //#endregion
9
+ //#region src/validation/zod/duck-dsn-zod-schema.d.ts
10
+ declare const duckDsnZodSchema: z.ZodPipe<z.ZodString, z.ZodPipe<z.ZodTransform<{
11
+ type: "memory";
12
+ alias: string;
13
+ options?: {
14
+ accessMode?: "READ_ONLY" | "READ_WRITE" | undefined;
15
+ compress?: boolean | undefined;
16
+ type?: "DUCKDB" | "SQLITE" | "MYSQL" | "PostgreSQL" | undefined;
17
+ blockSize?: number | undefined;
18
+ rowGroupSize?: number | undefined;
19
+ storageVersion?: string | undefined;
20
+ encryptionKey?: string | undefined;
21
+ encryptionCipher?: "CBC" | "CTR" | "GCM" | undefined;
22
+ } | undefined;
23
+ } | {
24
+ type: "filesystem";
25
+ path: string;
26
+ alias: string;
27
+ options?: {
28
+ accessMode?: "READ_ONLY" | "READ_WRITE" | undefined;
29
+ compress?: boolean | undefined;
30
+ type?: "DUCKDB" | "SQLITE" | "MYSQL" | "PostgreSQL" | undefined;
31
+ blockSize?: number | undefined;
32
+ rowGroupSize?: number | undefined;
33
+ storageVersion?: string | undefined;
34
+ encryptionKey?: string | undefined;
35
+ encryptionCipher?: "CBC" | "CTR" | "GCM" | undefined;
36
+ } | undefined;
37
+ }, string>, z.ZodDiscriminatedUnion<[z.ZodObject<{
38
+ type: z.ZodLiteral<"memory">;
39
+ alias: z.ZodString;
40
+ options: z.ZodOptional<z.ZodObject<{
41
+ accessMode: z.ZodOptional<z.ZodEnum<{
42
+ READ_ONLY: "READ_ONLY";
43
+ READ_WRITE: "READ_WRITE";
44
+ }>>;
45
+ compress: z.ZodOptional<z.ZodBoolean>;
46
+ type: z.ZodOptional<z.ZodEnum<{
47
+ DUCKDB: "DUCKDB";
48
+ SQLITE: "SQLITE";
49
+ MYSQL: "MYSQL";
50
+ PostgreSQL: "PostgreSQL";
51
+ }>>;
52
+ blockSize: z.ZodOptional<z.ZodInt32>;
53
+ rowGroupSize: z.ZodOptional<z.ZodInt32>;
54
+ storageVersion: z.ZodOptional<z.ZodString>;
55
+ encryptionKey: z.ZodOptional<z.ZodString>;
56
+ encryptionCipher: z.ZodOptional<z.ZodEnum<{
57
+ CBC: "CBC";
58
+ CTR: "CTR";
59
+ GCM: "GCM";
60
+ }>>;
61
+ }, z.core.$strict>>;
62
+ }, z.core.$strict>, z.ZodObject<{
63
+ type: z.ZodLiteral<"filesystem">;
64
+ path: z.ZodString;
65
+ alias: z.ZodString;
66
+ options: z.ZodOptional<z.ZodObject<{
67
+ accessMode: z.ZodOptional<z.ZodEnum<{
68
+ READ_ONLY: "READ_ONLY";
69
+ READ_WRITE: "READ_WRITE";
70
+ }>>;
71
+ compress: z.ZodOptional<z.ZodBoolean>;
72
+ type: z.ZodOptional<z.ZodEnum<{
73
+ DUCKDB: "DUCKDB";
74
+ SQLITE: "SQLITE";
75
+ MYSQL: "MYSQL";
76
+ PostgreSQL: "PostgreSQL";
77
+ }>>;
78
+ blockSize: z.ZodOptional<z.ZodInt32>;
79
+ rowGroupSize: z.ZodOptional<z.ZodInt32>;
80
+ storageVersion: z.ZodOptional<z.ZodString>;
81
+ encryptionKey: z.ZodOptional<z.ZodString>;
82
+ encryptionCipher: z.ZodOptional<z.ZodEnum<{
83
+ CBC: "CBC";
84
+ CTR: "CTR";
85
+ GCM: "GCM";
86
+ }>>;
87
+ }, z.core.$strict>>;
88
+ }, z.core.$strict>], "type">>>;
89
+ //#endregion
9
90
  //#region src/validation/zod/duck-validators-zod.d.ts
10
91
  /**
11
92
  * Common validators for duckdb parameters, tables...
@@ -23,15 +104,9 @@ declare const duckValidatorsZod: {
23
104
  * Validate duckdb objects names like table, alias, and schemas
24
105
  * for validity.
25
106
  */
26
- readonly aliasName: _$zod.ZodString;
27
- readonly schemaName: _$zod.ZodString;
28
- readonly tableName: _$zod.ZodString;
107
+ readonly aliasName: z.ZodString;
108
+ readonly schemaName: z.ZodString;
109
+ readonly tableName: z.ZodString;
29
110
  };
30
111
  //#endregion
31
- //#region src/validation/zod/is-parsable-duck-dsn-zod.d.ts
32
- declare const isParsableDuckDsnZod: (dsn: unknown) => boolean;
33
- //#endregion
34
- //#region src/validation/zod/parse-duck-dsn-zod.d.ts
35
- declare const parseDuckDSNZod: (dsn: string) => DuckConnectionParams;
36
- //#endregion
37
- export { assertValidAliasName, assertValidSchemaName, assertValidTableName, duckAllConnectionOptionsZodSchema, duckConnectionParamsZodSchema, duckValidatorsZod, isParsableDuckDsnZod, parseDuckDSNZod };
112
+ export { assertValidAliasName, assertValidSchemaName, assertValidTableName, duckAllConnectionOptionsZodSchema, duckConnectionParamsZodSchema, duckDsnZodSchema, duckValidatorsZod };
@@ -1,2 +1,2 @@
1
- import { a as assertValidTableName, c as duckValidatorsZod, i as assertValidSchemaName, n as parseDuckDSNZod, o as duckAllConnectionOptionsZodSchema, r as assertValidAliasName, s as duckConnectionParamsZodSchema, t as isParsableDuckDsnZod } from "../../zod-DcIc8xQC.mjs";
2
- export { assertValidAliasName, assertValidSchemaName, assertValidTableName, duckAllConnectionOptionsZodSchema, duckConnectionParamsZodSchema, duckValidatorsZod, isParsableDuckDsnZod, parseDuckDSNZod };
1
+ import { a as duckAllConnectionOptionsZodSchema, i as assertValidTableName, n as assertValidAliasName, o as duckConnectionParamsZodSchema, r as assertValidSchemaName, s as duckValidatorsZod, t as duckDsnZodSchema } from "../../zod-CuPjTLv8.mjs";
2
+ export { assertValidAliasName, assertValidSchemaName, assertValidTableName, duckAllConnectionOptionsZodSchema, duckConnectionParamsZodSchema, duckDsnZodSchema, duckValidatorsZod };
@@ -1,115 +1,8 @@
1
+ import { a as duckStorageVersionRegexp, i as duckIdentifierNameRegex, n as duckdbReservedKeywordsSet, r as duckConnectionsOptions } from "./duck-reserved-keywords-B8XUjnaY.mjs";
2
+ import isSafeFilename from "is-safe-filename";
1
3
  import * as z from "zod";
2
4
  import { parseDsn } from "@httpx/dsn-parser";
3
- //#region src/validation/core/base-validators.ts
4
- const duckIdentifierNameRegex = /^[a-z_]\w*$/i;
5
- const duckStorageVersionRegexp = /^v?\d{1,4}\.\d{1,4}\.\d{1,4}$/;
6
- //#endregion
7
- //#region src/validation/core/duck-reserved-keywords.ts
8
- /**
9
- * DuckDB reserved keywords that cannot be used as unquoted identifiers.
10
- * @see https://duckdb.org/docs/sql/keywords-and-identifiers.html
11
- */
12
- const duckReservedKeywords = [
13
- "ALL",
14
- "ANALYSE",
15
- "ANALYZE",
16
- "AND",
17
- "ANY",
18
- "ARRAY",
19
- "AS",
20
- "ASC",
21
- "ASYMMETRIC",
22
- "BOTH",
23
- "CASE",
24
- "CAST",
25
- "CHECK",
26
- "COLLATE",
27
- "COLUMN",
28
- "CONSTRAINT",
29
- "CREATE",
30
- "CROSS",
31
- "CURRENT_CATALOG",
32
- "CURRENT_DATE",
33
- "CURRENT_ROLE",
34
- "CURRENT_SCHEMA",
35
- "CURRENT_TIME",
36
- "CURRENT_TIMESTAMP",
37
- "CURRENT_USER",
38
- "DEFAULT",
39
- "DEFERRABLE",
40
- "DESC",
41
- "DISTINCT",
42
- "DO",
43
- "ELSE",
44
- "END",
45
- "EXCEPT",
46
- "EXISTS",
47
- "EXTRACT",
48
- "FALSE",
49
- "FETCH",
50
- "FOR",
51
- "FOREIGN",
52
- "FROM",
53
- "GRANT",
54
- "GROUP",
55
- "HAVING",
56
- "IF",
57
- "ILIKE",
58
- "IN",
59
- "INITIALLY",
60
- "INNER",
61
- "INTERSECT",
62
- "INTO",
63
- "IS",
64
- "ISNULL",
65
- "JOIN",
66
- "LATERAL",
67
- "LEADING",
68
- "LEFT",
69
- "LIKE",
70
- "LIMIT",
71
- "LOCALTIME",
72
- "LOCALTIMESTAMP",
73
- "NATURAL",
74
- "NOT",
75
- "NOTNULL",
76
- "NULL",
77
- "OFFSET",
78
- "ON",
79
- "ONLY",
80
- "OR",
81
- "ORDER",
82
- "OUTER",
83
- "OVERLAPS",
84
- "PLACING",
85
- "PRIMARY",
86
- "REFERENCES",
87
- "RETURNING",
88
- "RIGHT",
89
- "ROW",
90
- "SELECT",
91
- "SESSION_USER",
92
- "SIMILAR",
93
- "SOME",
94
- "SYMMETRIC",
95
- "TABLE",
96
- "THEN",
97
- "TO",
98
- "TRAILING",
99
- "TRUE",
100
- "UNION",
101
- "UNIQUE",
102
- "USING",
103
- "VARIADIC",
104
- "VERBOSE",
105
- "WHEN",
106
- "WHERE",
107
- "WINDOW",
108
- "WITH"
109
- ];
110
- //#endregion
111
5
  //#region src/validation/zod/duck-identifier-zod-schema.ts
112
- const duckdbReservedKeywordsSet = new Set(duckReservedKeywords.map((k) => k.toUpperCase()));
113
6
  /**
114
7
  * Check whether a table name identifier is valid
115
8
  */
@@ -135,26 +28,28 @@ const duckValidatorsZod = {
135
28
  //#endregion
136
29
  //#region src/validation/zod/duck-connection-params-zod-schema.ts
137
30
  const duckAllConnectionOptionsZodSchema = z.strictObject({
138
- accessMode: z.optional(z.enum(["READ_ONLY", "READ_WRITE"])),
31
+ accessMode: z.optional(z.enum(duckConnectionsOptions.accessModes)),
139
32
  compress: z.optional(z.boolean()),
140
- type: z.optional(z.enum(["DUCKDB", "SQLITE"])),
33
+ type: z.optional(z.enum(duckConnectionsOptions.types)),
141
34
  blockSize: z.optional(z.int32().min(16384).max(262144)),
142
35
  rowGroupSize: z.optional(z.int32().positive()),
143
36
  storageVersion: z.optional(z.string().startsWith("v").regex(duckStorageVersionRegexp)),
144
37
  encryptionKey: z.optional(z.string().min(8)),
145
- encryptionCipher: z.optional(z.enum([
146
- "CBC",
147
- "CTR",
148
- "GCM"
149
- ]))
38
+ encryptionCipher: z.optional(z.enum(duckConnectionsOptions.encryptionCiphers))
150
39
  });
151
40
  const duckConnectionParamsZodSchema = z.discriminatedUnion("type", [z.strictObject({
152
41
  type: z.literal("memory"),
153
42
  alias: duckValidatorsZod.aliasName,
154
43
  options: z.optional(duckAllConnectionOptionsZodSchema)
155
44
  }), z.strictObject({
156
- type: z.literal("duckdb"),
157
- path: z.string().min(4).endsWith(".db"),
45
+ type: z.literal("filesystem"),
46
+ path: z.string().refine((path) => {
47
+ const filename = path.replace("\\", "/").split("/").at(-1);
48
+ return typeof filename === "string" && isSafeFilename(filename);
49
+ }, { message: "Invalid database filename - it must be a safe filename (no path traversal, no absolute paths, no reserved names, etc.)" }).refine((path) => {
50
+ const pathname = "/" + path.replace("\\", "/").split("/").slice(0, -1).filter(Boolean).join("/");
51
+ return pathname.length > 0 && pathname.startsWith("/") && !pathname.includes("..");
52
+ }, { message: "Invalid database pathname - it must be an absolute path with no traversal" }),
158
53
  alias: duckValidatorsZod.aliasName,
159
54
  options: z.optional(duckAllConnectionOptionsZodSchema)
160
55
  })]);
@@ -179,29 +74,26 @@ function assertValidTableName(tableName) {
179
74
  if (parsed.error) throw createAssertError(`'${tableName}' is not a valid table name: ${parsed.error.message}`);
180
75
  }
181
76
  //#endregion
182
- //#region src/validation/zod/parse-duck-dsn-zod.ts
183
- const parseDuckDSNZod = (dsn) => {
77
+ //#region src/validation/zod/duck-dsn-zod-schema.ts
78
+ const duckDsnZodSchema = z.string().pipe(z.preprocess((dsn, ctx) => {
184
79
  const result = parseDsn(dsn);
185
- if (!result.success) throw new Error(`Invalid DuckDB DSN - ${result.message}`);
186
- const parsed = result.value;
187
- const { path, ...options } = parsed.params ?? {};
188
- return duckConnectionParamsZodSchema.parse({
189
- type: parsed.host,
190
- alias: parsed.db,
191
- ...path ? { path } : {},
192
- options: { ...options }
193
- });
194
- };
195
- //#endregion
196
- //#region src/validation/zod/is-parsable-duck-dsn-zod.ts
197
- const isParsableDuckDsnZod = (dsn) => {
198
- if (typeof dsn !== "string") return false;
199
- try {
200
- parseDuckDSNZod(dsn);
201
- return true;
202
- } catch {
203
- return false;
80
+ if (result.success) {
81
+ const parsed = result.value;
82
+ const { path, ...options } = parsed.params ?? {};
83
+ return {
84
+ type: parsed.host,
85
+ alias: parsed.db,
86
+ ...path ? { path } : {},
87
+ options: { ...options }
88
+ };
89
+ } else {
90
+ ctx.issues.push({
91
+ code: "custom",
92
+ message: result.message,
93
+ input: dsn
94
+ });
95
+ return z.NEVER;
204
96
  }
205
- };
97
+ }, duckConnectionParamsZodSchema));
206
98
  //#endregion
207
- export { assertValidTableName as a, duckValidatorsZod as c, assertValidSchemaName as i, duckReservedKeywords as l, parseDuckDSNZod as n, duckAllConnectionOptionsZodSchema as o, assertValidAliasName as r, duckConnectionParamsZodSchema as s, isParsableDuckDsnZod as t };
99
+ export { duckAllConnectionOptionsZodSchema as a, assertValidTableName as i, assertValidAliasName as n, duckConnectionParamsZodSchema as o, assertValidSchemaName as r, duckValidatorsZod as s, duckDsnZodSchema as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowblade/sqlduck",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -12,6 +12,10 @@
12
12
  "types": "./dist/validation/zod/index.d.mts",
13
13
  "default": "./dist/validation/zod/index.mjs"
14
14
  },
15
+ "./valibot": {
16
+ "types": "./dist/validation/valibot/index.d.mts",
17
+ "default": "./dist/validation/valibot/index.mjs"
18
+ },
15
19
  "./package.json": "./package.json"
16
20
  },
17
21
  "author": {
@@ -49,48 +53,55 @@
49
53
  "fix-staged": "lint-staged --allow-empty",
50
54
  "check-dist": "es-check --config=.escheckrc.json",
51
55
  "check-pub": "publint",
52
- "check-size": "size-limit"
56
+ "check-size-disabled": "size-limit"
53
57
  },
54
58
  "dependencies": {
55
59
  "@flowblade/core": "^0.2.26",
56
60
  "@flowblade/source-duckdb": "^0.20.1",
57
61
  "@flowblade/sql-tag": "^0.3.2",
58
- "@httpx/assert": "^0.16.8",
62
+ "@httpx/assert": "^0.16.9",
59
63
  "@httpx/dsn-parser": "^1.9.9",
60
64
  "@httpx/plain-object": "^2.1.8",
61
65
  "@logtape/logtape": "^2.0.5",
62
66
  "@standard-schema/spec": "^1.1.0",
63
- "p-queue": "9.1.0",
67
+ "is-safe-filename": "0.1.1",
68
+ "p-queue": "9.1.1",
64
69
  "zod": "^4.3.6"
65
70
  },
66
71
  "peerDependencies": {
67
- "@duckdb/node-api": "^1.5.0-r.1"
72
+ "@duckdb/node-api": "^1.5.0-r.1",
73
+ "valibot": "^1.3.1"
74
+ },
75
+ "peerDependenciesMeta": {
76
+ "valibot": {
77
+ "optional": true
78
+ }
68
79
  },
69
80
  "devDependencies": {
70
- "@belgattitude/eslint-config-bases": "8.10.0",
81
+ "@belgattitude/eslint-config-bases": "8.12.0",
71
82
  "@dotenvx/dotenvx": "1.59.1",
72
83
  "@duckdb/node-api": "1.5.1-r.1",
73
84
  "@faker-js/faker": "10.4.0",
74
85
  "@flowblade/source-kysely": "^1.3.0",
75
- "@httpx/assert": "0.16.8",
86
+ "@httpx/assert": "0.16.9",
76
87
  "@mitata/counters": "0.0.8",
77
88
  "@size-limit/esbuild": "12.0.1",
78
89
  "@size-limit/file": "12.0.1",
79
90
  "@testcontainers/mssqlserver": "11.13.0",
80
91
  "@total-typescript/ts-reset": "0.6.1",
81
- "@types/node": "25.5.0",
82
- "@typescript-eslint/eslint-plugin": "8.58.0",
83
- "@typescript-eslint/parser": "8.58.0",
84
- "@typescript/native-preview": "7.0.0-dev.20260324.1",
85
- "@vitest/coverage-v8": "4.1.2",
86
- "@vitest/ui": "4.1.2",
92
+ "@types/node": "25.5.2",
93
+ "@typescript-eslint/eslint-plugin": "8.58.1",
94
+ "@typescript-eslint/parser": "8.58.1",
95
+ "@typescript/native-preview": "7.0.0-dev.20260406.1",
96
+ "@vitest/coverage-v8": "4.1.3",
97
+ "@vitest/ui": "4.1.3",
87
98
  "ansis": "4.2.0",
88
99
  "browserslist-to-esbuild": "2.1.1",
89
100
  "core-js": "3.49.0",
90
101
  "cross-env": "10.1.0",
91
- "es-check": "9.6.3",
102
+ "es-check": "9.6.4",
92
103
  "es-toolkit": "1.45.1",
93
- "esbuild": "0.27.4",
104
+ "esbuild": "0.28.0",
94
105
  "eslint": "8.57.1",
95
106
  "execa": "9.6.1",
96
107
  "is-in-ci": "2.0.0",
@@ -111,7 +122,8 @@
111
122
  "typedoc": "0.28.18",
112
123
  "typedoc-plugin-markdown": "4.11.0",
113
124
  "typescript": "6.0.2",
114
- "vitest": "4.1.2"
125
+ "valibot": "1.3.1",
126
+ "vitest": "4.1.3"
115
127
  },
116
128
  "files": [
117
129
  "dist"