@flowblade/sqlduck 0.14.0 → 0.16.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 +29 -29
- package/dist/{duck-reserved-keywords-CUmaTbrD.mjs → duck-reserved-keywords-B8XUjnaY.mjs} +14 -6
- package/dist/index.d.mts +10 -1
- package/dist/index.mjs +241 -15
- package/dist/validation/valibot/index.d.mts +10 -10
- package/dist/validation/valibot/index.mjs +3 -7
- package/dist/validation/zod/index.mjs +1 -1
- package/dist/{zod-BRUUIdGW.mjs → zod-CuPjTLv8.mjs} +4 -8
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -131,32 +131,32 @@ const queryResult = await dbDuckDbMemoryConn.query<{
|
|
|
131
131
|
### Node 24
|
|
132
132
|
|
|
133
133
|
```
|
|
134
|
-
RUN v4.1.
|
|
134
|
+
RUN v4.1.4 /home/sebastien/github/flowblade/packages/sqlduck
|
|
135
135
|
|
|
136
136
|
|
|
137
|
-
✓ bench/appender.bench.ts > appender benches
|
|
138
|
-
name hz min max mean p75 p99 p995 p999
|
|
139
|
-
· duckdb appender memory, count: 100000, chunk size 2048 2.
|
|
140
|
-
· duckdb appender file, count: 100000, chunk size 2048 1.
|
|
141
|
-
· duckdb appender, count: 100000, chunk size 1024
|
|
137
|
+
✓ bench/appender.bench.ts > appender benches 3412ms
|
|
138
|
+
name hz min max mean p75 p99 p995 p999 rme samples
|
|
139
|
+
· duckdb appender memory, count: 100000, chunk size 2048 2.8872 295.84 396.87 346.35 396.87 396.87 396.87 396.87 ±185.36% 2
|
|
140
|
+
· duckdb appender file, count: 100000, chunk size 2048 1.7908 558.40 558.40 558.40 558.40 558.40 558.40 558.40 ±0.00% 1
|
|
141
|
+
· duckdb appender, count: 100000, chunk size 1024 1.9967 500.82 500.82 500.82 500.82 500.82 500.82 500.82 ±0.00% 1
|
|
142
142
|
|
|
143
|
-
✓ bench/stream.bench.ts > Bench stream
|
|
144
|
-
name
|
|
145
|
-
· rowToColumnsChunk with chunkSize 2048 (count: 100000)
|
|
146
|
-
· mapFakeRowStream with chunkSize 2048 (count: 100000)
|
|
143
|
+
✓ bench/stream.bench.ts > Bench stream 2140ms
|
|
144
|
+
name hz min max mean p75 p99 p995 p999 rme samples
|
|
145
|
+
· rowToColumnsChunk with chunkSize 2048 (count: 100000) 11.8099 63.0430 172.82 84.6748 81.4757 172.82 172.82 172.82 ±27.12% 10
|
|
146
|
+
· mapFakeRowStream with chunkSize 2048 (count: 100000) 10.0442 79.5805 125.81 99.5603 112.35 125.81 125.81 125.81 ±12.04% 10
|
|
147
147
|
|
|
148
|
-
✓ bench/table-create.bench.ts > Bench getTableCreateFromZod
|
|
148
|
+
✓ bench/table-create.bench.ts > Bench getTableCreateFromZod 617ms
|
|
149
149
|
name hz min max mean p75 p99 p995 p999 rme samples
|
|
150
|
-
· getTableCreateFromZod
|
|
150
|
+
· getTableCreateFromZod 27,583.03 0.0226 2.7818 0.0363 0.0403 0.1185 0.1583 0.4321 ±1.66% 13792
|
|
151
151
|
|
|
152
152
|
BENCH Summary
|
|
153
153
|
|
|
154
154
|
duckdb appender memory, count: 100000, chunk size 2048 - bench/appender.bench.ts > appender benches
|
|
155
|
-
1.
|
|
156
|
-
1.
|
|
155
|
+
1.45x faster than duckdb appender, count: 100000, chunk size 1024
|
|
156
|
+
1.61x faster than duckdb appender file, count: 100000, chunk size 2048
|
|
157
157
|
|
|
158
158
|
rowToColumnsChunk with chunkSize 2048 (count: 100000) - bench/stream.bench.ts > Bench stream
|
|
159
|
-
1.
|
|
159
|
+
1.18x faster than mapFakeRowStream with chunkSize 2048 (count: 100000)
|
|
160
160
|
|
|
161
161
|
getTableCreateFromZod - bench/table-create.bench.ts > Bench getTableCreateFromZod
|
|
162
162
|
```
|
|
@@ -164,34 +164,34 @@ const queryResult = await dbDuckDbMemoryConn.query<{
|
|
|
164
164
|
### Bun 1.3.11
|
|
165
165
|
|
|
166
166
|
```
|
|
167
|
-
RUN v4.1.
|
|
167
|
+
RUN v4.1.4 /home/sebastien/github/flowblade/packages/sqlduck
|
|
168
168
|
|
|
169
169
|
|
|
170
|
-
✓ bench/appender.bench.ts > appender benches
|
|
170
|
+
✓ bench/appender.bench.ts > appender benches 3357ms
|
|
171
171
|
name hz min max mean p75 p99 p995 p999 rme samples
|
|
172
|
-
· duckdb appender memory, count: 100000, chunk size 2048 2.
|
|
173
|
-
· duckdb appender file, count: 100000, chunk size 2048 1.
|
|
174
|
-
· duckdb appender, count: 100000, chunk size 1024
|
|
172
|
+
· duckdb appender memory, count: 100000, chunk size 2048 2.9741 315.71 356.77 336.24 356.77 356.77 356.77 356.77 ±77.59% 2
|
|
173
|
+
· duckdb appender file, count: 100000, chunk size 2048 1.8953 527.62 527.62 527.62 527.62 527.62 527.62 527.62 ±0.00% 1
|
|
174
|
+
· duckdb appender, count: 100000, chunk size 1024 1.7803 561.70 561.70 561.70 561.70 561.70 561.70 561.70 ±0.00% 1
|
|
175
175
|
|
|
176
|
-
✓ bench/stream.bench.ts > Bench stream
|
|
177
|
-
name
|
|
178
|
-
· rowToColumnsChunk with chunkSize 2048 (count: 100000)
|
|
179
|
-
· mapFakeRowStream with chunkSize 2048 (count: 100000)
|
|
176
|
+
✓ bench/stream.bench.ts > Bench stream 2058ms
|
|
177
|
+
name hz min max mean p75 p99 p995 p999 rme samples
|
|
178
|
+
· rowToColumnsChunk with chunkSize 2048 (count: 100000) 12.0130 60.7081 111.32 83.2432 99.7116 111.32 111.32 111.32 ±14.15% 10
|
|
179
|
+
· mapFakeRowStream with chunkSize 2048 (count: 100000) 10.9710 76.4253 145.14 91.1493 91.1424 145.14 145.14 145.14 ±16.04% 10
|
|
180
180
|
|
|
181
|
-
✓ bench/table-create.bench.ts > Bench getTableCreateFromZod
|
|
181
|
+
✓ bench/table-create.bench.ts > Bench getTableCreateFromZod 621ms
|
|
182
182
|
name hz min max mean p75 p99 p995 p999 rme samples
|
|
183
|
-
· getTableCreateFromZod
|
|
183
|
+
· getTableCreateFromZod 27,472.91 0.0185 4.6226 0.0364 0.0369 0.1137 0.1476 1.9138 ±5.20% 13737
|
|
184
184
|
|
|
185
185
|
BENCH Summary
|
|
186
186
|
|
|
187
187
|
rowToColumnsChunk with chunkSize 2048 (count: 100000) - bench/stream.bench.ts > Bench stream
|
|
188
|
-
1.
|
|
188
|
+
1.09x faster than mapFakeRowStream with chunkSize 2048 (count: 100000)
|
|
189
189
|
|
|
190
190
|
getTableCreateFromZod - bench/table-create.bench.ts > Bench getTableCreateFromZod
|
|
191
191
|
|
|
192
192
|
duckdb appender memory, count: 100000, chunk size 2048 - bench/appender.bench.ts > appender benches
|
|
193
|
-
1.
|
|
194
|
-
1.
|
|
193
|
+
1.57x faster than duckdb appender file, count: 100000, chunk size 2048
|
|
194
|
+
1.67x faster than duckdb appender, count: 100000, chunk size 1024
|
|
195
195
|
|
|
196
196
|
```
|
|
197
197
|
|
|
@@ -3,12 +3,20 @@ const duckIdentifierNameRegex = /^[a-z_]\w*$/i;
|
|
|
3
3
|
const duckStorageVersionRegexp = /^v?\d{1,4}\.\d{1,4}\.\d{1,4}$/;
|
|
4
4
|
//#endregion
|
|
5
5
|
//#region src/validation/core/duck-connections-options.ts
|
|
6
|
-
const duckConnectionsOptions = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
+
};
|
|
12
20
|
//#endregion
|
|
13
21
|
//#region src/validation/core/duck-reserved-keywords.ts
|
|
14
22
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -81,7 +81,7 @@ declare class Table {
|
|
|
81
81
|
}
|
|
82
82
|
//#endregion
|
|
83
83
|
//#region src/table/table-schema-zod.type.d.ts
|
|
84
|
-
type ZodSchemaSupportedTypes = z.ZodString | z.ZodNumber | z.ZodInt | z.ZodInt32 | z.ZodUInt32 | z.ZodBigInt | z.ZodBoolean | z.ZodDate | z.ZodISODateTime | z.ZodISOTime | z.ZodISODate | z.ZodEmail | z.ZodURL | z.ZodUUID | z.ZodCUID | z.ZodCUID2 | z.ZodULID;
|
|
84
|
+
type ZodSchemaSupportedTypes = z.ZodString | z.ZodNumber | z.ZodInt | z.ZodInt32 | z.ZodUInt32 | z.ZodBigInt | z.ZodBoolean | z.ZodDate | z.ZodISODateTime | z.ZodISOTime | z.ZodISODate | z.ZodEmail | z.ZodURL | z.ZodUUID | z.ZodCUID | z.ZodCUID2 | z.ZodULID | z.ZodEnum;
|
|
85
85
|
type TableSchemaZod = z.ZodObject<Record<string, ZodSchemaSupportedTypes | z.ZodNullable<ZodSchemaSupportedTypes> | z.ZodCodec | z.ZodNullable<z.ZodCodec>>>;
|
|
86
86
|
//#endregion
|
|
87
87
|
//#region src/table/get-table-create-from-zod.d.ts
|
|
@@ -283,6 +283,15 @@ declare class DuckDatabaseManager {
|
|
|
283
283
|
analyze: () => Promise<boolean>;
|
|
284
284
|
checkpoint: (dbAlias: string) => Promise<boolean>;
|
|
285
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
|
+
}>;
|
|
286
295
|
}
|
|
287
296
|
//#endregion
|
|
288
297
|
//#region src/config/flowblade-logtape-sqlduck.config.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { t as duckReservedKeywords } from "./duck-reserved-keywords-
|
|
2
|
-
import { n as assertValidAliasName, o as duckConnectionParamsZodSchema, s as duckValidatorsZod } from "./zod-
|
|
3
|
-
import { BIGINT, BOOLEAN, DOUBLE, DuckDBDataChunk,
|
|
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, DuckDBTimestampMillisecondsValue, DuckDBTypeId, ENUM, FLOAT, HUGEINT, INTEGER, SMALLINT, TIMESTAMP, TIMESTAMP_MS, TINYINT, UBIGINT, UHUGEINT, UINTEGER, USMALLINT, UTINYINT, UUID, VARCHAR } from "@duckdb/node-api";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import { basename, dirname } from "node:path";
|
|
5
7
|
import * as z from "zod";
|
|
6
8
|
import { assertNever } from "@httpx/assert";
|
|
7
9
|
import { isPlainObject } from "@httpx/plain-object";
|
|
@@ -122,12 +124,191 @@ const createOnDataAppendedCollector = () => {
|
|
|
122
124
|
};
|
|
123
125
|
};
|
|
124
126
|
//#endregion
|
|
127
|
+
//#region src/converter/duck-value-converter.ts
|
|
128
|
+
const stringTimestampRegexp = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d{3,6})?Z?$/i;
|
|
129
|
+
const dateRegexp = /^\d{4}-\d{2}-\d{2}$/;
|
|
130
|
+
const createDuckValueConverterTypeError = (params) => {
|
|
131
|
+
let serializableValue;
|
|
132
|
+
try {
|
|
133
|
+
serializableValue = JSON.stringify(params.value);
|
|
134
|
+
} catch {
|
|
135
|
+
serializableValue = "<unserializable>";
|
|
136
|
+
}
|
|
137
|
+
return /* @__PURE__ */ new TypeError(`[DuckValueConverter.${params.method}]: Unsupported type ${typeof params.value} with value ${serializableValue}`);
|
|
138
|
+
};
|
|
139
|
+
var DuckValueConverter = class {
|
|
140
|
+
toUUID = (value) => {
|
|
141
|
+
if (typeof value === "bigint") return value;
|
|
142
|
+
else if (typeof value === "string") return BigInt("0x" + value.replaceAll("-", ""));
|
|
143
|
+
if (value === void 0 || value === null) return null;
|
|
144
|
+
throw createDuckValueConverterTypeError({
|
|
145
|
+
method: "toUUID",
|
|
146
|
+
value
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
toStringEnum = (value) => {
|
|
150
|
+
if (typeof value === "string") return value;
|
|
151
|
+
if (value === void 0 || value === null) return null;
|
|
152
|
+
throw createDuckValueConverterTypeError({
|
|
153
|
+
method: "toStringEnum",
|
|
154
|
+
value
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
toBigIntString = (value) => {
|
|
158
|
+
if (typeof value === "string") return value;
|
|
159
|
+
if (typeof value === "number" || typeof value === "bigint") return value.toString(10);
|
|
160
|
+
if (value === void 0 || value === null) return null;
|
|
161
|
+
throw createDuckValueConverterTypeError({
|
|
162
|
+
method: "toBigIntString",
|
|
163
|
+
value
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
toTimestampMs = (value) => {
|
|
167
|
+
if (value instanceof Date) return new DuckDBTimestampMillisecondsValue(BigInt(value.getTime()));
|
|
168
|
+
if (value === void 0 || value === null) return null;
|
|
169
|
+
if (typeof value === "string") {
|
|
170
|
+
const len = value.length;
|
|
171
|
+
if (len > 18 && len < 31 && stringTimestampRegexp.test(value)) {
|
|
172
|
+
const date = /* @__PURE__ */ new Date(value + (value.endsWith("Z") ? "" : "Z"));
|
|
173
|
+
return new DuckDBTimestampMillisecondsValue(BigInt(date.getTime()));
|
|
174
|
+
}
|
|
175
|
+
if (len === 10 && dateRegexp.test(value)) {
|
|
176
|
+
const date = /* @__PURE__ */ new Date(value + "T00:00:00Z");
|
|
177
|
+
return new DuckDBTimestampMillisecondsValue(BigInt(date.getTime()));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (typeof value === "bigint") return new DuckDBTimestampMillisecondsValue(value);
|
|
181
|
+
if (typeof value === "number") return new DuckDBTimestampMillisecondsValue(BigInt(value));
|
|
182
|
+
throw createDuckValueConverterTypeError({
|
|
183
|
+
method: "toTimestampMs",
|
|
184
|
+
value
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/converter/create-duck-column-converters.ts
|
|
190
|
+
const createDuckColumnConverters = (duckTypes) => {
|
|
191
|
+
const convMap = /* @__PURE__ */ new Map();
|
|
192
|
+
const converter = new DuckValueConverter();
|
|
193
|
+
for (const [key, duckType] of Object.entries(duckTypes)) {
|
|
194
|
+
let conv;
|
|
195
|
+
const duckTypeId = duckType.typeId;
|
|
196
|
+
switch (duckTypeId) {
|
|
197
|
+
case DuckDBTypeId.TIMESTAMP_MS:
|
|
198
|
+
conv = converter.toTimestampMs;
|
|
199
|
+
break;
|
|
200
|
+
case DuckDBTypeId.BIGINT:
|
|
201
|
+
case DuckDBTypeId.UBIGINT:
|
|
202
|
+
case DuckDBTypeId.HUGEINT:
|
|
203
|
+
case DuckDBTypeId.UHUGEINT:
|
|
204
|
+
case DuckDBTypeId.INTEGER:
|
|
205
|
+
case DuckDBTypeId.UINTEGER:
|
|
206
|
+
case DuckDBTypeId.BIGNUM:
|
|
207
|
+
conv = converter.toBigIntString;
|
|
208
|
+
break;
|
|
209
|
+
case DuckDBTypeId.ENUM:
|
|
210
|
+
conv = converter.toStringEnum;
|
|
211
|
+
break;
|
|
212
|
+
case DuckDBTypeId.UUID:
|
|
213
|
+
conv = converter.toUUID;
|
|
214
|
+
break;
|
|
215
|
+
case DuckDBTypeId.BIT:
|
|
216
|
+
case DuckDBTypeId.BOOLEAN:
|
|
217
|
+
case DuckDBTypeId.TINYINT:
|
|
218
|
+
case DuckDBTypeId.USMALLINT:
|
|
219
|
+
case DuckDBTypeId.UTINYINT:
|
|
220
|
+
case DuckDBTypeId.VARCHAR:
|
|
221
|
+
case DuckDBTypeId.SMALLINT:
|
|
222
|
+
conv = false;
|
|
223
|
+
break;
|
|
224
|
+
default: throw new Error(`Unsupported duck type ${duckTypeId} / ${duckType.toString()} for column '${key}'`);
|
|
225
|
+
}
|
|
226
|
+
if (conv !== false) convMap.set(key, conv);
|
|
227
|
+
}
|
|
228
|
+
return convMap;
|
|
229
|
+
};
|
|
230
|
+
//#endregion
|
|
125
231
|
//#region src/config/flowblade-logtape-sqlduck.config.ts
|
|
126
232
|
const flowbladeLogtapeSqlduckConfig = { categories: ["flowblade", "sqlduck"] };
|
|
127
233
|
//#endregion
|
|
128
234
|
//#region src/logger/sqlduck-default-logtape-logger.ts
|
|
129
235
|
const sqlduckDefaultLogtapeLogger = getLogger(flowbladeLogtapeSqlduckConfig.categories);
|
|
130
236
|
//#endregion
|
|
237
|
+
//#region src/filesystem/file-system-utils.ts
|
|
238
|
+
var FileSystemUtils = class {
|
|
239
|
+
#logger;
|
|
240
|
+
constructor(params) {
|
|
241
|
+
this.#logger = params?.logger ?? sqlduckDefaultLogtapeLogger.with({ source: "FileSystemUtils" });
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Create a directory recursively if it doesn't exist
|
|
245
|
+
*
|
|
246
|
+
* @throws Error if it can't be created
|
|
247
|
+
*/
|
|
248
|
+
createDirectory = (path) => {
|
|
249
|
+
try {
|
|
250
|
+
fs.mkdirSync(path, { recursive: true });
|
|
251
|
+
} catch (err) {
|
|
252
|
+
if (err.code !== "EEXIST") throw err;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
/**
|
|
256
|
+
* Create a directory recursively if it doesn't exist and ensure it's writable
|
|
257
|
+
*/
|
|
258
|
+
createAndEnsureWritableDirectory = (label, path) => {
|
|
259
|
+
if (path === void 0) return;
|
|
260
|
+
if (!fs.existsSync(path)) try {
|
|
261
|
+
this.createDirectory(path);
|
|
262
|
+
} catch (e) {
|
|
263
|
+
throw new Error(`Failed to create ${label} '${path}' - ${e?.message ?? ""}`);
|
|
264
|
+
}
|
|
265
|
+
if (!fs.statSync(path).isDirectory()) throw new Error(`${label} '${path}' must be a directory`);
|
|
266
|
+
try {
|
|
267
|
+
fs.accessSync(path, fs.constants.W_OK);
|
|
268
|
+
} catch {
|
|
269
|
+
throw new Error(`${label} '${path}' must be writable`);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* Check if a path is a regular file and exists
|
|
274
|
+
*/
|
|
275
|
+
isFile = (path) => {
|
|
276
|
+
try {
|
|
277
|
+
return fs.statSync(path).isFile();
|
|
278
|
+
} catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Check if a path is a directory and exists
|
|
284
|
+
*/
|
|
285
|
+
isDirectory = (path) => {
|
|
286
|
+
try {
|
|
287
|
+
return fs.statSync(path).isDirectory();
|
|
288
|
+
} catch {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
parsePath = (path) => {
|
|
293
|
+
const dir = dirname(path);
|
|
294
|
+
if (dir.trim() === "") throw new Error(`Invalid path, missing directory '${path}'`);
|
|
295
|
+
const base = basename(path);
|
|
296
|
+
if (base.trim() === "") throw new Error(`Invalid path, missing filename '${path}'`);
|
|
297
|
+
return {
|
|
298
|
+
directory: dir,
|
|
299
|
+
filename: base
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
removeFileIfExists = (path) => {
|
|
303
|
+
try {
|
|
304
|
+
fs.rmSync(path);
|
|
305
|
+
return true;
|
|
306
|
+
} catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
//#endregion
|
|
131
312
|
//#region src/objects/database.ts
|
|
132
313
|
var Database = class {
|
|
133
314
|
#params;
|
|
@@ -207,6 +388,7 @@ var DuckDatabaseAttachCommand = class {
|
|
|
207
388
|
var DuckDatabaseManager = class {
|
|
208
389
|
#conn;
|
|
209
390
|
#logger;
|
|
391
|
+
#fs;
|
|
210
392
|
constructor(conn, params) {
|
|
211
393
|
this.#conn = conn;
|
|
212
394
|
this.#logger = params?.logger ?? sqlduckDefaultLogtapeLogger.with({ source: "DuckDatabaseManager" });
|
|
@@ -229,7 +411,12 @@ var DuckDatabaseManager = class {
|
|
|
229
411
|
attach = async (dbParams, options) => {
|
|
230
412
|
const params = duckConnectionParamsZodSchema.parse(dbParams);
|
|
231
413
|
const rawSql = new DuckDatabaseAttachCommand(params, options).getRawSql();
|
|
232
|
-
|
|
414
|
+
const { behaviour } = options ?? {};
|
|
415
|
+
await this.#executeRawSqlCommand([
|
|
416
|
+
`attach(${params.alias}`,
|
|
417
|
+
behaviour ?? null,
|
|
418
|
+
")"
|
|
419
|
+
].filter(Boolean).join(","), rawSql);
|
|
233
420
|
return new Database({ alias: params.alias });
|
|
234
421
|
};
|
|
235
422
|
attachOrReplace = async (dbParams) => {
|
|
@@ -272,6 +459,32 @@ var DuckDatabaseManager = class {
|
|
|
272
459
|
await this.#executeRawSqlCommand("vacuum()", "VACUUM");
|
|
273
460
|
return true;
|
|
274
461
|
};
|
|
462
|
+
/**
|
|
463
|
+
* Helper to create an initial database file.
|
|
464
|
+
*/
|
|
465
|
+
createDatabaseFile = async (params) => {
|
|
466
|
+
const startTime = Date.now();
|
|
467
|
+
const { path, createDirectory = true } = params;
|
|
468
|
+
const fs = this.#getFs();
|
|
469
|
+
if (fs.isFile(path)) return { status: "exists" };
|
|
470
|
+
if (createDirectory) {
|
|
471
|
+
const { directory } = fs.parsePath(path);
|
|
472
|
+
fs.createAndEnsureWritableDirectory("database file directory", directory);
|
|
473
|
+
}
|
|
474
|
+
const instanceCache = new DuckDBInstanceCache();
|
|
475
|
+
try {
|
|
476
|
+
(await (await instanceCache.getOrCreateInstance(path)).connect()).closeSync();
|
|
477
|
+
const timeMs = Math.round(Date.now() - startTime);
|
|
478
|
+
this.#logger.info(`DuckDatabaseManager.createDatabaseFile('${path}') in ${timeMs}ms`, {
|
|
479
|
+
timeMs,
|
|
480
|
+
path
|
|
481
|
+
});
|
|
482
|
+
} catch (e) {
|
|
483
|
+
this.#logger.error(`DuckDatabaseManager.createDatabaseFile('${path}') failed - ${e?.message ?? ""}`, { path });
|
|
484
|
+
throw e;
|
|
485
|
+
}
|
|
486
|
+
return { status: "created" };
|
|
487
|
+
};
|
|
275
488
|
#executeRawSqlCommand = async (name, rawSql) => {
|
|
276
489
|
const startTime = Date.now();
|
|
277
490
|
try {
|
|
@@ -291,6 +504,10 @@ var DuckDatabaseManager = class {
|
|
|
291
504
|
throw new Error(msg, { cause: e });
|
|
292
505
|
}
|
|
293
506
|
};
|
|
507
|
+
#getFs = () => {
|
|
508
|
+
if (this.#fs === void 0) this.#fs = new FileSystemUtils({ logger: this.#logger });
|
|
509
|
+
return this.#fs;
|
|
510
|
+
};
|
|
294
511
|
};
|
|
295
512
|
//#endregion
|
|
296
513
|
//#region src/table/get-duckdb-number-column-type.ts
|
|
@@ -329,7 +546,10 @@ const createOptions = {
|
|
|
329
546
|
const duckDbTypesMap = new Map([
|
|
330
547
|
["VARCHAR", VARCHAR],
|
|
331
548
|
["BIGINT", BIGINT],
|
|
549
|
+
["UBIGINT", UBIGINT],
|
|
550
|
+
["HUGEINT", HUGEINT],
|
|
332
551
|
["TIMESTAMP", TIMESTAMP],
|
|
552
|
+
["TIMESTAMP_MS", TIMESTAMP_MS],
|
|
333
553
|
["UUID", UUID],
|
|
334
554
|
["BOOLEAN", BOOLEAN],
|
|
335
555
|
["INTEGER", INTEGER],
|
|
@@ -353,9 +573,10 @@ const getTableCreateFromZod = (params) => {
|
|
|
353
573
|
if (duckdbType !== void 0 && duckDbTypesMap.has(duckdbType)) c.duckdbType = duckDbTypesMap.get(duckdbType);
|
|
354
574
|
else switch (type) {
|
|
355
575
|
case "string":
|
|
356
|
-
|
|
576
|
+
if (Array.isArray(def.enum)) c.duckdbType = ENUM(def.enum);
|
|
577
|
+
else switch (format) {
|
|
357
578
|
case "date-time":
|
|
358
|
-
c.duckdbType =
|
|
579
|
+
c.duckdbType = TIMESTAMP_MS;
|
|
359
580
|
break;
|
|
360
581
|
case "int64":
|
|
361
582
|
c.duckdbType = BIGINT;
|
|
@@ -428,11 +649,6 @@ const createTableFromZod = async (params) => {
|
|
|
428
649
|
};
|
|
429
650
|
//#endregion
|
|
430
651
|
//#region src/utils/rows-to-columns-chunks.ts
|
|
431
|
-
const toDuckValue = (value) => {
|
|
432
|
-
if (value instanceof Date) return new DuckDBTimestampValue(BigInt(value.getTime() * 1e3));
|
|
433
|
-
if (typeof value === "bigint") return value.toString(10);
|
|
434
|
-
return value === void 0 ? null : value;
|
|
435
|
-
};
|
|
436
652
|
/**
|
|
437
653
|
* Similar to `rowsToColumns` but yields results in chunks to avoid buffering
|
|
438
654
|
* the entire dataset in memory. Each yielded item is a columns array for up to
|
|
@@ -443,14 +659,17 @@ const toDuckValue = (value) => {
|
|
|
443
659
|
* yields: [[['1','2'], ['A','B']], [['3'], ['C']]]
|
|
444
660
|
*/
|
|
445
661
|
async function* rowsToColumnsChunks(params) {
|
|
446
|
-
const { rows, chunkSize } = params;
|
|
662
|
+
const { rows, chunkSize, transformers } = params;
|
|
447
663
|
if (!Number.isSafeInteger(chunkSize) || chunkSize <= 0) throw new Error(`chunkSize must be a positive integer, got ${chunkSize}`);
|
|
448
664
|
const first = await rows.next();
|
|
449
665
|
if (first.done) return;
|
|
450
666
|
const keys = Object.keys(first.value);
|
|
451
667
|
let columns = keys.map(() => []);
|
|
452
668
|
let rowsInChunk = 0;
|
|
453
|
-
keys.forEach((k, i) =>
|
|
669
|
+
keys.forEach((k, i) => {
|
|
670
|
+
const fn = transformers?.get(k);
|
|
671
|
+
columns[i].push(fn === void 0 ? first.value[k] : fn(first.value[k]));
|
|
672
|
+
});
|
|
454
673
|
rowsInChunk++;
|
|
455
674
|
if (rowsInChunk >= chunkSize) {
|
|
456
675
|
yield columns;
|
|
@@ -458,7 +677,10 @@ async function* rowsToColumnsChunks(params) {
|
|
|
458
677
|
rowsInChunk = 0;
|
|
459
678
|
}
|
|
460
679
|
for await (const row of rows) {
|
|
461
|
-
keys.forEach((k, i) =>
|
|
680
|
+
keys.forEach((k, i) => {
|
|
681
|
+
const fn = transformers?.get(k);
|
|
682
|
+
columns[i].push(fn === void 0 ? row[k] : fn(row[k]));
|
|
683
|
+
});
|
|
462
684
|
rowsInChunk++;
|
|
463
685
|
if (rowsInChunk >= chunkSize) {
|
|
464
686
|
yield columns;
|
|
@@ -530,11 +752,15 @@ var SqlDuck = class {
|
|
|
530
752
|
});
|
|
531
753
|
const appender = await this.#conn.createAppender(table.tableName, table.schemaName, table.databaseName);
|
|
532
754
|
const chunkTypes = Array.from(columnTypes.values());
|
|
755
|
+
const transformers = createDuckColumnConverters(Object.fromEntries(Array.from(columnTypes).map(([key, duckType]) => {
|
|
756
|
+
return [key, duckType];
|
|
757
|
+
})));
|
|
533
758
|
let totalRows = 0;
|
|
534
759
|
const dataAppendedCollector = createOnDataAppendedCollector();
|
|
535
760
|
const columnStream = rowsToColumnsChunks({
|
|
536
761
|
rows: rowStream,
|
|
537
|
-
chunkSize
|
|
762
|
+
chunkSize,
|
|
763
|
+
transformers
|
|
538
764
|
});
|
|
539
765
|
let appendedChunkCount = 0;
|
|
540
766
|
try {
|
|
@@ -2,42 +2,42 @@ import * as v from "valibot";
|
|
|
2
2
|
|
|
3
3
|
//#region src/validation/valibot/duck-connection-params-valibot-schema.d.ts
|
|
4
4
|
declare const duckAllConnectionOptionsValibotSchema: v.ObjectSchema<{
|
|
5
|
-
readonly accessMode: v.OptionalSchema<v.PicklistSchema<["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
|
|
5
|
+
readonly accessMode: v.OptionalSchema<v.PicklistSchema<readonly ["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
|
|
6
6
|
readonly compress: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
7
7
|
readonly type: v.OptionalSchema<v.PicklistSchema<readonly ["DUCKDB", "SQLITE", "MYSQL", "PostgreSQL"], undefined>, undefined>;
|
|
8
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
9
|
readonly rowGroupSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
|
|
10
10
|
readonly storageVersion: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.StartsWithAction<string, "v", undefined>, v.RegexAction<string, undefined>]>, undefined>;
|
|
11
11
|
readonly encryptionKey: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 8, undefined>]>, undefined>;
|
|
12
|
-
readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<["CBC", "CTR", "GCM"], undefined>, undefined>;
|
|
12
|
+
readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<readonly ["CBC", "CTR", "GCM"], undefined>, undefined>;
|
|
13
13
|
}, undefined>;
|
|
14
14
|
type DuckAllConnectionOptionsValibotSchema = v.InferOutput<typeof duckAllConnectionOptionsValibotSchema>;
|
|
15
15
|
declare const duckConnectionParamsValibotSchema: v.VariantSchema<"type", [v.ObjectSchema<{
|
|
16
16
|
readonly type: v.LiteralSchema<"memory", undefined>;
|
|
17
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
18
|
readonly options: v.OptionalSchema<v.ObjectSchema<{
|
|
19
|
-
readonly accessMode: v.OptionalSchema<v.PicklistSchema<["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
|
|
19
|
+
readonly accessMode: v.OptionalSchema<v.PicklistSchema<readonly ["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
|
|
20
20
|
readonly compress: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
21
21
|
readonly type: v.OptionalSchema<v.PicklistSchema<readonly ["DUCKDB", "SQLITE", "MYSQL", "PostgreSQL"], undefined>, undefined>;
|
|
22
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
23
|
readonly rowGroupSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
|
|
24
24
|
readonly storageVersion: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.StartsWithAction<string, "v", undefined>, v.RegexAction<string, undefined>]>, undefined>;
|
|
25
25
|
readonly encryptionKey: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 8, undefined>]>, undefined>;
|
|
26
|
-
readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<["CBC", "CTR", "GCM"], undefined>, undefined>;
|
|
26
|
+
readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<readonly ["CBC", "CTR", "GCM"], undefined>, undefined>;
|
|
27
27
|
}, undefined>, undefined>;
|
|
28
28
|
}, undefined>, v.ObjectSchema<{
|
|
29
29
|
readonly type: v.LiteralSchema<"filesystem", undefined>;
|
|
30
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
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
32
|
readonly options: v.OptionalSchema<v.ObjectSchema<{
|
|
33
|
-
readonly accessMode: v.OptionalSchema<v.PicklistSchema<["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
|
|
33
|
+
readonly accessMode: v.OptionalSchema<v.PicklistSchema<readonly ["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
|
|
34
34
|
readonly compress: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
35
35
|
readonly type: v.OptionalSchema<v.PicklistSchema<readonly ["DUCKDB", "SQLITE", "MYSQL", "PostgreSQL"], undefined>, undefined>;
|
|
36
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
37
|
readonly rowGroupSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
|
|
38
38
|
readonly storageVersion: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.StartsWithAction<string, "v", undefined>, v.RegexAction<string, undefined>]>, undefined>;
|
|
39
39
|
readonly encryptionKey: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 8, undefined>]>, undefined>;
|
|
40
|
-
readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<["CBC", "CTR", "GCM"], undefined>, undefined>;
|
|
40
|
+
readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<readonly ["CBC", "CTR", "GCM"], undefined>, undefined>;
|
|
41
41
|
}, undefined>, undefined>;
|
|
42
42
|
}, undefined>], undefined>;
|
|
43
43
|
//#endregion
|
|
@@ -73,28 +73,28 @@ declare const duckDsnValibotSchema: v.SchemaWithPipe<readonly [v.StringSchema<un
|
|
|
73
73
|
readonly type: v.LiteralSchema<"memory", undefined>;
|
|
74
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
75
|
readonly options: v.OptionalSchema<v.ObjectSchema<{
|
|
76
|
-
readonly accessMode: v.OptionalSchema<v.PicklistSchema<["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
|
|
76
|
+
readonly accessMode: v.OptionalSchema<v.PicklistSchema<readonly ["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
|
|
77
77
|
readonly compress: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
78
78
|
readonly type: v.OptionalSchema<v.PicklistSchema<readonly ["DUCKDB", "SQLITE", "MYSQL", "PostgreSQL"], undefined>, undefined>;
|
|
79
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
80
|
readonly rowGroupSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
|
|
81
81
|
readonly storageVersion: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.StartsWithAction<string, "v", undefined>, v.RegexAction<string, undefined>]>, undefined>;
|
|
82
82
|
readonly encryptionKey: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 8, undefined>]>, undefined>;
|
|
83
|
-
readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<["CBC", "CTR", "GCM"], undefined>, undefined>;
|
|
83
|
+
readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<readonly ["CBC", "CTR", "GCM"], undefined>, undefined>;
|
|
84
84
|
}, undefined>, undefined>;
|
|
85
85
|
}, undefined>, v.ObjectSchema<{
|
|
86
86
|
readonly type: v.LiteralSchema<"filesystem", undefined>;
|
|
87
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
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
89
|
readonly options: v.OptionalSchema<v.ObjectSchema<{
|
|
90
|
-
readonly accessMode: v.OptionalSchema<v.PicklistSchema<["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
|
|
90
|
+
readonly accessMode: v.OptionalSchema<v.PicklistSchema<readonly ["READ_ONLY", "READ_WRITE"], undefined>, undefined>;
|
|
91
91
|
readonly compress: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
92
92
|
readonly type: v.OptionalSchema<v.PicklistSchema<readonly ["DUCKDB", "SQLITE", "MYSQL", "PostgreSQL"], undefined>, undefined>;
|
|
93
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
94
|
readonly rowGroupSize: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
|
|
95
95
|
readonly storageVersion: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.StartsWithAction<string, "v", undefined>, v.RegexAction<string, undefined>]>, undefined>;
|
|
96
96
|
readonly encryptionKey: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 8, undefined>]>, undefined>;
|
|
97
|
-
readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<["CBC", "CTR", "GCM"], undefined>, undefined>;
|
|
97
|
+
readonly encryptionCipher: v.OptionalSchema<v.PicklistSchema<readonly ["CBC", "CTR", "GCM"], undefined>, undefined>;
|
|
98
98
|
}, undefined>, undefined>;
|
|
99
99
|
}, undefined>], undefined>]>;
|
|
100
100
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as duckStorageVersionRegexp, i as duckIdentifierNameRegex, n as duckdbReservedKeywordsSet, r as duckConnectionsOptions } from "../../duck-reserved-keywords-
|
|
1
|
+
import { a as duckStorageVersionRegexp, i as duckIdentifierNameRegex, n as duckdbReservedKeywordsSet, r as duckConnectionsOptions } from "../../duck-reserved-keywords-B8XUjnaY.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";
|
|
@@ -26,18 +26,14 @@ const duckValidatorsValibot = {
|
|
|
26
26
|
//#endregion
|
|
27
27
|
//#region src/validation/valibot/duck-connection-params-valibot-schema.ts
|
|
28
28
|
const duckAllConnectionOptionsValibotSchema = v.object({
|
|
29
|
-
accessMode: v.optional(v.picklist(
|
|
29
|
+
accessMode: v.optional(v.picklist(duckConnectionsOptions.accessModes)),
|
|
30
30
|
compress: v.optional(v.boolean()),
|
|
31
31
|
type: v.optional(v.picklist(duckConnectionsOptions.types)),
|
|
32
32
|
blockSize: v.optional(v.pipe(v.number(), v.integer(), v.minValue(16384), v.maxValue(262144))),
|
|
33
33
|
rowGroupSize: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))),
|
|
34
34
|
storageVersion: v.optional(v.pipe(v.string(), v.startsWith("v"), v.regex(duckStorageVersionRegexp))),
|
|
35
35
|
encryptionKey: v.optional(v.pipe(v.string(), v.minLength(8))),
|
|
36
|
-
encryptionCipher: v.optional(v.picklist(
|
|
37
|
-
"CBC",
|
|
38
|
-
"CTR",
|
|
39
|
-
"GCM"
|
|
40
|
-
]))
|
|
36
|
+
encryptionCipher: v.optional(v.picklist(duckConnectionsOptions.encryptionCiphers))
|
|
41
37
|
});
|
|
42
38
|
const duckConnectionParamsValibotSchema = v.variant("type", [v.object({
|
|
43
39
|
type: v.literal("memory"),
|
|
@@ -1,2 +1,2 @@
|
|
|
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-
|
|
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
2
|
export { assertValidAliasName, assertValidSchemaName, assertValidTableName, duckAllConnectionOptionsZodSchema, duckConnectionParamsZodSchema, duckDsnZodSchema, duckValidatorsZod };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as duckStorageVersionRegexp, i as duckIdentifierNameRegex, n as duckdbReservedKeywordsSet, r as duckConnectionsOptions } from "./duck-reserved-keywords-
|
|
1
|
+
import { a as duckStorageVersionRegexp, i as duckIdentifierNameRegex, n as duckdbReservedKeywordsSet, r as duckConnectionsOptions } from "./duck-reserved-keywords-B8XUjnaY.mjs";
|
|
2
2
|
import isSafeFilename from "is-safe-filename";
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
import { parseDsn } from "@httpx/dsn-parser";
|
|
@@ -28,18 +28,14 @@ const duckValidatorsZod = {
|
|
|
28
28
|
//#endregion
|
|
29
29
|
//#region src/validation/zod/duck-connection-params-zod-schema.ts
|
|
30
30
|
const duckAllConnectionOptionsZodSchema = z.strictObject({
|
|
31
|
-
accessMode: z.optional(z.enum(
|
|
31
|
+
accessMode: z.optional(z.enum(duckConnectionsOptions.accessModes)),
|
|
32
32
|
compress: z.optional(z.boolean()),
|
|
33
33
|
type: z.optional(z.enum(duckConnectionsOptions.types)),
|
|
34
34
|
blockSize: z.optional(z.int32().min(16384).max(262144)),
|
|
35
35
|
rowGroupSize: z.optional(z.int32().positive()),
|
|
36
36
|
storageVersion: z.optional(z.string().startsWith("v").regex(duckStorageVersionRegexp)),
|
|
37
37
|
encryptionKey: z.optional(z.string().min(8)),
|
|
38
|
-
encryptionCipher: z.optional(z.enum(
|
|
39
|
-
"CBC",
|
|
40
|
-
"CTR",
|
|
41
|
-
"GCM"
|
|
42
|
-
]))
|
|
38
|
+
encryptionCipher: z.optional(z.enum(duckConnectionsOptions.encryptionCiphers))
|
|
43
39
|
});
|
|
44
40
|
const duckConnectionParamsZodSchema = z.discriminatedUnion("type", [z.strictObject({
|
|
45
41
|
type: z.literal("memory"),
|
|
@@ -52,7 +48,7 @@ const duckConnectionParamsZodSchema = z.discriminatedUnion("type", [z.strictObje
|
|
|
52
48
|
return typeof filename === "string" && isSafeFilename(filename);
|
|
53
49
|
}, { message: "Invalid database filename - it must be a safe filename (no path traversal, no absolute paths, no reserved names, etc.)" }).refine((path) => {
|
|
54
50
|
const pathname = "/" + path.replace("\\", "/").split("/").slice(0, -1).filter(Boolean).join("/");
|
|
55
|
-
return pathname.length > 0 && pathname.startsWith("/") && pathname.includes("..")
|
|
51
|
+
return pathname.length > 0 && pathname.startsWith("/") && !pathname.includes("..");
|
|
56
52
|
}, { message: "Invalid database pathname - it must be an absolute path with no traversal" }),
|
|
57
53
|
alias: duckValidatorsZod.aliasName,
|
|
58
54
|
options: z.optional(duckAllConnectionOptionsZodSchema)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowblade/sqlduck",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"fix-staged": "lint-staged --allow-empty",
|
|
54
54
|
"check-dist": "es-check --config=.escheckrc.json",
|
|
55
55
|
"check-pub": "publint",
|
|
56
|
-
"check-size": "size-limit"
|
|
56
|
+
"check-size-disabled": "size-limit"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@flowblade/core": "^0.2.26",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"@logtape/logtape": "^2.0.5",
|
|
66
66
|
"@standard-schema/spec": "^1.1.0",
|
|
67
67
|
"is-safe-filename": "0.1.1",
|
|
68
|
-
"p-queue": "9.1.
|
|
68
|
+
"p-queue": "9.1.2",
|
|
69
69
|
"zod": "^4.3.6"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
@@ -90,11 +90,11 @@
|
|
|
90
90
|
"@testcontainers/mssqlserver": "11.13.0",
|
|
91
91
|
"@total-typescript/ts-reset": "0.6.1",
|
|
92
92
|
"@types/node": "25.5.2",
|
|
93
|
-
"@typescript-eslint/eslint-plugin": "8.58.
|
|
94
|
-
"@typescript-eslint/parser": "8.58.
|
|
93
|
+
"@typescript-eslint/eslint-plugin": "8.58.1",
|
|
94
|
+
"@typescript-eslint/parser": "8.58.1",
|
|
95
95
|
"@typescript/native-preview": "7.0.0-dev.20260406.1",
|
|
96
|
-
"@vitest/coverage-v8": "4.1.
|
|
97
|
-
"@vitest/ui": "4.1.
|
|
96
|
+
"@vitest/coverage-v8": "4.1.4",
|
|
97
|
+
"@vitest/ui": "4.1.4",
|
|
98
98
|
"ansis": "4.2.0",
|
|
99
99
|
"browserslist-to-esbuild": "2.1.1",
|
|
100
100
|
"core-js": "3.49.0",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"typedoc-plugin-markdown": "4.11.0",
|
|
124
124
|
"typescript": "6.0.2",
|
|
125
125
|
"valibot": "1.3.1",
|
|
126
|
-
"vitest": "4.1.
|
|
126
|
+
"vitest": "4.1.4"
|
|
127
127
|
},
|
|
128
128
|
"files": [
|
|
129
129
|
"dist"
|