@flowblade/sqlduck 0.10.0 → 0.12.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
@@ -2,8 +2,81 @@
2
2
 
3
3
  > Currently experimental
4
4
 
5
+
6
+ - 🛡️ DuckDB table creation from Zod schemas.
7
+ - 🧩 Easily ingest data from generators or async iterables.
8
+
9
+
5
10
  ## Quick start
6
11
 
12
+ ### Create a database connection
13
+
14
+ ```typescript
15
+ import { DuckDBInstance } from '@duckdb/node-api';
16
+ DuckDBInstance.create(undefined, {
17
+ access_mode: 'READ_WRITE',
18
+ max_memory: '512M',
19
+ });
20
+ export const conn = await instance.connect();
21
+ ```
22
+
23
+ ### Append data to a database
24
+
25
+ ```typescript
26
+ import { SqlDuck, DuckDatabaseManager } from "@flowblade/sqlduck";
27
+ import * as z from "zod";
28
+ import { conn } from "./db.config.ts";
29
+
30
+ const dbManager = new DuckDatabaseManager(conn);
31
+ const database = await dbManager.attach({
32
+ type: 'memory', // can be 'duckdb', ...
33
+ alias: 'mydb',
34
+ options: { COMPRESS: 'false' },
35
+ });
36
+
37
+ const sqlDuck = new SqlDuck({ conn });
38
+
39
+ // Define a zod schema, it will be used to create the table
40
+ const userSchema = z.object({
41
+ id: z.int32().min(1).meta({ primaryKey: true }),
42
+ name: z.string(),
43
+ });
44
+
45
+ // Example of a datasource (can be generator, async generator, async iterable)
46
+ async function* getUsers(): AsyncIterableIterator<
47
+ z.infer<typeof userSchema>
48
+ > {
49
+ // database or api call
50
+ yield { id: 1, name: 'John' };
51
+ yield { id: 2, name: 'Jane' };
52
+ }
53
+
54
+ // Create a table from the schema and the datasource
55
+ const result = await sqlDuck.toTable({
56
+ table: new Table({ name: 'user', database: database.alias }),
57
+ schema: userSchema, // The schema to use to create the table
58
+ rowStream: getUsers(), // The async iterable that yields rows
59
+ // 👇Optional:
60
+ chunkSize: 2048, // Number of rows to append when using duckdb appender. Default is 2048
61
+ onDataAppended: ({ timeMs, totalRows, rowsPerSecond }) => {
62
+ console.log(
63
+ `Appended ${totalRows} in time ${timeMs}ms, est: ${rowsPerSecond} rows/s`
64
+ );
65
+ },
66
+ // Optional table creation options
67
+ createOptions: {
68
+ create: 'CREATE_OR_REPLACE',
69
+ },
70
+ });
71
+
72
+ console.log(`Inserted ${result.totalRows} rows in ${result.timeMs}ms`);
73
+ console.log(`Table created with DDL: ${result.createTableDDL}`);
74
+
75
+ const reader = await conn.runAndReadAll('select * from mydb.user');
76
+ const rows = reader.getRowObjectsJS();
77
+ // [{id: 1, name: 'John'}, {id: 2, name: 'Jane'}]]
78
+ ```
79
+
7
80
  ### Create a memory table
8
81
 
9
82
  ```typescript
@@ -35,7 +108,6 @@ const result = sqlDuck.toTable({
35
108
  onDataAppended: ({ total }) => {
36
109
  console.log(`Appended ${total} rows so far`);
37
110
  },
38
- onDataAppendedBatchSize: 4096, // Call onDataAppended every 4096 rows
39
111
  // Optional table creation options
40
112
  createOptions: {
41
113
  create: "CREATE_OR_REPLACE",
@@ -62,30 +134,31 @@ const queryResult = await dbDuckDbMemoryConn.query<{
62
134
  RUN v4.1.1 /home/sebastien/github/flowblade/packages/sqlduck
63
135
 
64
136
 
65
- ✓ bench/appender.bench.ts > appender benches 66030ms
66
- name hz min max mean p75 p99 p995 p999 rme samples
67
- · duckdb appender, count: 1000000, chunk size 2048 0.0642 15,577.01 15,577.01 15,577.01 15,577.01 15,577.01 15,577.01 15,577.01 ±0.00% 1
68
- · duckdb appender, count: 1000000, chunk size 1024 0.0579 17,263.44 17,263.44 17,263.44 17,263.44 17,263.44 17,263.44 17,263.44 ±0.00% 1
137
+ ✓ bench/appender.bench.ts > appender benches 4157ms
138
+ name hz min max mean p75 p99 p995 p999 rme samples
139
+ · duckdb appender memory, count: 100000, chunk size 2048 2.6950 357.08 385.05 371.06 385.05 385.05 385.05 385.05 ±47.90% 2
140
+ · duckdb appender file, count: 100000, chunk size 2048 1.4218 703.35 703.35 703.35 703.35 703.35 703.35 703.35 ±0.00% 1
141
+ · duckdb appender, count: 100000, chunk size 1024 2.5157 391.12 403.89 397.50 403.89 403.89 403.89 403.89 ±20.41% 2
69
142
 
70
- ✓ bench/stream.bench.ts > Bench stream 22923ms
71
- name hz min max mean p75 p99 p995 p999 rme samples
72
- · rowToColumnsChunk with chunkSize 2048 (count: 1000000) 1.2126 742.59 906.65 824.67 863.91 906.65 906.65 906.65 ±4.26% 10
73
- · mapFakeRowStream with chunkSize 2048 (count: 1000000) 0.8049 1,123.18 1,498.68 1,242.43 1,257.91 1,498.68 1,498.68 1,498.68 ±6.40% 10
143
+ ✓ bench/stream.bench.ts > Bench stream 2809ms
144
+ name hz min max mean p75 p99 p995 p999 rme samples
145
+ · rowToColumnsChunk with chunkSize 2048 (count: 100000) 9.2627 87.7271 151.56 107.96 116.92 151.56 151.56 151.56 ±15.98% 10
146
+ · mapFakeRowStream with chunkSize 2048 (count: 100000) 7.1479 125.04 168.13 139.90 152.97 168.13 168.13 168.13 ±7.78% 10
74
147
 
75
- ✓ bench/table-create.bench.ts > Bench getTableCreateFromZod 615ms
148
+ ✓ bench/table-create.bench.ts > Bench getTableCreateFromZod 614ms
76
149
  name hz min max mean p75 p99 p995 p999 rme samples
77
- · getTableCreateFromZod 16,562.93 0.0242 2.5902 0.0604 0.0734 0.2555 0.3741 0.8135 ±2.32% 8282
150
+ · getTableCreateFromZod 18,899.24 0.0334 5.3351 0.0529 0.0546 0.1943 0.3087 0.7214 ±2.72% 9450
78
151
 
79
152
  BENCH Summary
80
153
 
81
- duckdb appender, count: 1000000, chunk size 2048 - bench/appender.bench.ts > appender benches
82
- 1.11x faster than duckdb appender, count: 1000000, chunk size 1024
154
+ duckdb appender memory, count: 100000, chunk size 2048 - bench/appender.bench.ts > appender benches
155
+ 1.07x faster than duckdb appender, count: 100000, chunk size 1024
156
+ 1.90x faster than duckdb appender file, count: 100000, chunk size 2048
83
157
 
84
- rowToColumnsChunk with chunkSize 2048 (count: 1000000) - bench/stream.bench.ts > Bench stream
85
- 1.51x faster than mapFakeRowStream with chunkSize 2048 (count: 1000000)
158
+ rowToColumnsChunk with chunkSize 2048 (count: 100000) - bench/stream.bench.ts > Bench stream
159
+ 1.30x faster than mapFakeRowStream with chunkSize 2048 (count: 100000)
86
160
 
87
161
  getTableCreateFromZod - bench/table-create.bench.ts > Bench getTableCreateFromZod
88
-
89
162
  ```
90
163
 
91
164
  ### Bun 1.3.11
@@ -94,29 +167,31 @@ const queryResult = await dbDuckDbMemoryConn.query<{
94
167
  RUN v4.1.1 /home/sebastien/github/flowblade/packages/sqlduck
95
168
 
96
169
 
97
- ✓ bench/appender.bench.ts > appender benches 36627ms
98
- name hz min max mean p75 p99 p995 p999 rme samples
99
- · duckdb appender, count: 1000000, chunk size 2048 0.1177 8,495.41 8,495.41 8,495.41 8,495.41 8,495.41 8,495.41 8,495.41 ±0.00% 1
100
- · duckdb appender, count: 1000000, chunk size 1024 0.1064 9,397.97 9,397.97 9,397.97 9,397.97 9,397.97 9,397.97 9,397.97 ±0.00% 1
170
+ ✓ bench/appender.bench.ts > appender benches 4159ms
171
+ name hz min max mean p75 p99 p995 p999 rme samples
172
+ · duckdb appender memory, count: 100000, chunk size 2048 2.6465 375.34 380.38 377.86 380.38 380.38 380.38 380.38 ±8.48% 2
173
+ · duckdb appender file, count: 100000, chunk size 2048 1.5016 665.98 665.98 665.98 665.98 665.98 665.98 665.98 ±0.00% 1
174
+ · duckdb appender, count: 100000, chunk size 1024 2.2828 413.11 463.01 438.06 463.01 463.01 463.01 463.01 ±72.39% 2
101
175
 
102
- ✓ bench/stream.bench.ts > Bench stream 23421ms
103
- name hz min max mean p75 p99 p995 p999 rme samples
104
- · rowToColumnsChunk with chunkSize 2048 (count: 1000000) 1.1378 801.60 1,080.22 878.91 910.91 1,080.22 1,080.22 1,080.22 ±6.85% 10
105
- · mapFakeRowStream with chunkSize 2048 (count: 1000000) 0.8118 1,130.36 1,448.99 1,231.78 1,268.45 1,448.99 1,448.99 1,448.99 ±5.34% 10
176
+ ✓ bench/stream.bench.ts > Bench stream 2690ms
177
+ name hz min max mean p75 p99 p995 p999 rme samples
178
+ · rowToColumnsChunk with chunkSize 2048 (count: 100000) 9.5675 95.6610 114.11 104.52 107.75 114.11 114.11 114.11 ±3.46% 10
179
+ · mapFakeRowStream with chunkSize 2048 (count: 100000) 7.6895 117.83 138.26 130.05 137.51 138.26 138.26 138.26 ±4.05% 10
106
180
 
107
- ✓ bench/table-create.bench.ts > Bench getTableCreateFromZod 622ms
181
+ ✓ bench/table-create.bench.ts > Bench getTableCreateFromZod 629ms
108
182
  name hz min max mean p75 p99 p995 p999 rme samples
109
- · getTableCreateFromZod 22,447.94 0.0210 5.4621 0.0445 0.0442 0.1657 0.2167 2.5852 ±5.37% 11224
183
+ · getTableCreateFromZod 18,892.06 0.0281 7.5844 0.0529 0.0516 0.1893 0.2477 3.2823 ±6.26% 9447
110
184
 
111
185
  BENCH Summary
112
186
 
113
- rowToColumnsChunk with chunkSize 2048 (count: 1000000) - bench/stream.bench.ts > Bench stream
114
- 1.40x faster than mapFakeRowStream with chunkSize 2048 (count: 1000000)
187
+ rowToColumnsChunk with chunkSize 2048 (count: 100000) - bench/stream.bench.ts > Bench stream
188
+ 1.24x faster than mapFakeRowStream with chunkSize 2048 (count: 100000)
115
189
 
116
190
  getTableCreateFromZod - bench/table-create.bench.ts > Bench getTableCreateFromZod
117
191
 
118
- duckdb appender, count: 1000000, chunk size 2048 - bench/appender.bench.ts > appender benches
119
- 1.11x faster than duckdb appender, count: 1000000, chunk size 1024
192
+ duckdb appender memory, count: 100000, chunk size 2048 - bench/appender.bench.ts > appender benches
193
+ 1.16x faster than duckdb appender, count: 100000, chunk size 1024
194
+ 1.76x faster than duckdb appender file, count: 100000, chunk size 2048
120
195
 
121
196
  ```
122
197
 
package/dist/index.d.mts CHANGED
@@ -1,6 +1,8 @@
1
- import * as _logtape_logtape0 from "@logtape/logtape";
2
- import { Logger } from "@logtape/logtape";
1
+ import { n as DuckConnectionParams } from "./types-DCqYqEsa.mjs";
2
+ import * as _$_duckdb_node_api0 from "@duckdb/node-api";
3
3
  import { DuckDBConnection, DuckDBType } from "@duckdb/node-api";
4
+ import * as _$_logtape_logtape0 from "@logtape/logtape";
5
+ import { Logger } from "@logtape/logtape";
4
6
  import * as z from "zod";
5
7
  import { ZodObject } from "zod";
6
8
 
@@ -23,11 +25,6 @@ type OnDataAppendedSyncCb = (stats: OnDataAppendedStats) => void;
23
25
  type OnDataAppendedAsyncCb = (stats: OnDataAppendedStats) => Promise<void>;
24
26
  type OnDataAppendedCb = OnDataAppendedSyncCb | OnDataAppendedAsyncCb;
25
27
  //#endregion
26
- //#region src/config/flowblade-logtape-sqlduck.config.d.ts
27
- declare const flowbladeLogtapeSqlduckConfig: {
28
- categories: string[];
29
- };
30
- //#endregion
31
28
  //#region src/helpers/duck-memory.d.ts
32
29
  declare const duckMemoryTags: readonly ["BASE_TABLE", "HASH_TABLE", "PARQUET_READER", "CSV_READER", "ORDER_BY", "ART_INDEX", "COLUMN_DATA", "METADATA", "OVERFLOW_STRINGS", "IN_MEMORY_TABLE", "ALLOCATOR", "EXTENSION", "TRANSACTION", "EXTERNAL_FILE_CACHE", "WINDOW", "OBJECT_CACHE"];
33
30
  type DuckMemoryTag = (typeof duckMemoryTags)[number];
@@ -56,10 +53,7 @@ declare class DuckMemory {
56
53
  getSummary: () => Promise<DuckMemorySummary>;
57
54
  }
58
55
  //#endregion
59
- //#region src/logger/sqlduck-default-logtape-logger.d.ts
60
- declare const sqlduckDefaultLogtapeLogger: _logtape_logtape0.Logger;
61
- //#endregion
62
- //#region src/table/table.d.ts
56
+ //#region src/objects/table.d.ts
63
57
  /**
64
58
  * Fully qualified table information
65
59
  */
@@ -144,6 +138,11 @@ type ToTableParams<TSchema extends TableSchemaZod> = {
144
138
  * Callback called each time a datachunk is appended to the table
145
139
  */
146
140
  onDataAppended?: OnDataAppendedCb;
141
+ /**
142
+ * Automatically checkpoint the table after all chunks have been appended.
143
+ * @default true
144
+ */
145
+ autoCheckpoint?: boolean;
147
146
  };
148
147
  type ToTableResult = {
149
148
  /**
@@ -208,4 +207,83 @@ declare const zodCodecs: {
208
207
  readonly bigintToString: z.ZodCodec<z.ZodBigInt, z.ZodString>;
209
208
  };
210
209
  //#endregion
211
- export { DuckMemory, type DuckMemoryTag, type OnDataAppendedCb, type OnDataAppendedStats, SqlDuck, type SqlDuckParams, Table, type ToTableParams, flowbladeLogtapeSqlduckConfig, getTableCreateFromZod, sqlduckDefaultLogtapeLogger, zodCodecs };
210
+ //#region src/objects/database.d.ts
211
+ type DatabaseProperties = {
212
+ alias: string;
213
+ };
214
+ declare class Database {
215
+ #private;
216
+ get alias(): string;
217
+ constructor(params: DatabaseProperties);
218
+ toJson(): {
219
+ type: string;
220
+ params: {
221
+ alias: string;
222
+ };
223
+ };
224
+ [Symbol.toStringTag](): string;
225
+ }
226
+ //#endregion
227
+ //#region src/validation/core/duck-reserved-keywords.d.ts
228
+ /**
229
+ * DuckDB reserved keywords that cannot be used as unquoted identifiers.
230
+ * @see https://duckdb.org/docs/sql/keywords-and-identifiers.html
231
+ */
232
+ 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"];
233
+ type DuckdbReservedKeywords = (typeof duckReservedKeywords)[number];
234
+ //#endregion
235
+ //#region src/manager/database/commands/duck-database-attach-command.d.ts
236
+ type Behaviour = 'OR REPLACE' | 'IF NOT EXISTS';
237
+ type DuckDatabaseAttachCommandOptions = {
238
+ behaviour?: Behaviour;
239
+ };
240
+ //#endregion
241
+ //#region src/manager/database/duck-database-manager.d.ts
242
+ declare class DuckDatabaseManager {
243
+ #private;
244
+ constructor(conn: DuckDBConnection, params?: {
245
+ logger?: Logger;
246
+ });
247
+ /**
248
+ * Attach a database to the current connection
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * const dbManager = new DuckDatabaseManager(conn);
253
+ * const database = dbManager.attach({
254
+ * type: 'memory', // can be 'duckdb', 's3'...
255
+ * alias: 'mydb',
256
+ * options: { COMPRESS: 'true' }
257
+ * });
258
+ *
259
+ * console.log(database.alias); // 'mydb'
260
+ * ```
261
+ */
262
+ attach: (dbParams: DuckConnectionParams, options?: DuckDatabaseAttachCommandOptions) => Promise<Database>;
263
+ attachOrReplace: (dbParams: DuckConnectionParams) => Promise<Database>;
264
+ attachIfNotExists: (dbParams: DuckConnectionParams) => Promise<Database>;
265
+ showDatabases: () => Promise<Record<string, _$_duckdb_node_api0.JS>[]>;
266
+ detach: (dbAlias: string) => Promise<boolean>;
267
+ detachIfExists: (dbAlias: string) => Promise<boolean>;
268
+ /**
269
+ * The statistics recomputed by the ANALYZE statement are only used for join order optimization.
270
+ *
271
+ * It is therefore recommended to recompute these statistics for improved join orders,
272
+ * especially after performing large updates (inserts and/or deletes).
273
+ *
274
+ * @link https://duckdb.org/docs/stable/sql/statements/analyze
275
+ */
276
+ analyze: () => Promise<boolean>;
277
+ checkpoint: (dbAlias: string) => Promise<boolean>;
278
+ vacuum: () => Promise<boolean>;
279
+ }
280
+ //#endregion
281
+ //#region src/config/flowblade-logtape-sqlduck.config.d.ts
282
+ declare const flowbladeLogtapeSqlduckConfig: {
283
+ categories: string[];
284
+ };
285
+ //#endregion
286
+ //#region src/logger/sqlduck-default-logtape-logger.d.ts
287
+ declare const sqlduckDefaultLogtapeLogger: _$_logtape_logtape0.Logger;
288
+ //#endregion
289
+ export { Database, type DuckConnectionParams, DuckDatabaseManager, DuckMemory, type DuckMemoryTag, type DuckdbReservedKeywords, type OnDataAppendedCb, type OnDataAppendedStats, SqlDuck, type SqlDuckParams, Table, type ToTableParams, duckReservedKeywords, flowbladeLogtapeSqlduckConfig, getTableCreateFromZod, sqlduckDefaultLogtapeLogger, zodCodecs };
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { getLogger } from "@logtape/logtape";
1
+ import { a as assertValidAliasName, c as duckValidatorsZod, i as duckConnectionParamsZodSchema, l as duckReservedKeywords } from "./zod-CwR_oehs.mjs";
2
2
  import { BIGINT, BOOLEAN, DOUBLE, DuckDBDataChunk, DuckDBTimestampValue, FLOAT, HUGEINT, INTEGER, SMALLINT, TIMESTAMP, TINYINT, UBIGINT, UHUGEINT, UINTEGER, USMALLINT, UTINYINT, UUID, VARCHAR } from "@duckdb/node-api";
3
+ import { getLogger } from "@logtape/logtape";
3
4
  import * as z from "zod";
4
- //#region src/config/flowblade-logtape-sqlduck.config.ts
5
- const flowbladeLogtapeSqlduckConfig = { categories: ["flowblade", "sqlduck"] };
6
- //#endregion
5
+ import { assertNever } from "@httpx/assert";
6
+ import { isPlainObject } from "@httpx/plain-object";
7
7
  //#region src/helpers/duck-exec.ts
8
8
  var DuckExec = class {
9
9
  #conn;
@@ -100,9 +100,6 @@ var DuckMemory = class {
100
100
  };
101
101
  };
102
102
  //#endregion
103
- //#region src/logger/sqlduck-default-logtape-logger.ts
104
- const sqlduckDefaultLogtapeLogger = getLogger(flowbladeLogtapeSqlduckConfig.categories);
105
- //#endregion
106
103
  //#region src/appender/data-appender-callback.ts
107
104
  const isOnDataAppendedAsyncCb = (v) => {
108
105
  return v.constructor.name === "AsyncFunction";
@@ -124,6 +121,177 @@ const createOnDataAppendedCollector = () => {
124
121
  };
125
122
  };
126
123
  //#endregion
124
+ //#region src/config/flowblade-logtape-sqlduck.config.ts
125
+ const flowbladeLogtapeSqlduckConfig = { categories: ["flowblade", "sqlduck"] };
126
+ //#endregion
127
+ //#region src/logger/sqlduck-default-logtape-logger.ts
128
+ const sqlduckDefaultLogtapeLogger = getLogger(flowbladeLogtapeSqlduckConfig.categories);
129
+ //#endregion
130
+ //#region src/objects/database.ts
131
+ var Database = class {
132
+ #params;
133
+ get alias() {
134
+ return this.#params.alias;
135
+ }
136
+ constructor(params) {
137
+ this.#params = params;
138
+ }
139
+ toJson() {
140
+ return {
141
+ type: "database",
142
+ params: { alias: this.#params.alias }
143
+ };
144
+ }
145
+ [Symbol.toStringTag]() {
146
+ return this.alias;
147
+ }
148
+ };
149
+ //#endregion
150
+ //#region src/manager/database/commands/duck-database-attach-command.ts
151
+ var DuckDatabaseAttachCommand = class {
152
+ options;
153
+ dbParams;
154
+ constructor(dbParams, options) {
155
+ this.dbParams = dbParams;
156
+ this.options = options ?? {};
157
+ }
158
+ getRawSql = () => {
159
+ const dbParams = this.dbParams;
160
+ const parts = ["ATTACH", this.options.behaviour].filter(Boolean);
161
+ const { type, alias } = dbParams;
162
+ switch (type) {
163
+ case "memory":
164
+ parts.push("':memory:'");
165
+ break;
166
+ case "duckdb":
167
+ parts.push(`'${dbParams.path}'`);
168
+ break;
169
+ default: assertNever(type);
170
+ }
171
+ if (alias !== null) parts.push("AS", `${alias}`);
172
+ const options = [];
173
+ if (isPlainObject(dbParams.options)) for (const [key, value] of Object.entries(dbParams.options)) switch (key) {
174
+ case "accessMode":
175
+ options.push(`${value}`);
176
+ break;
177
+ case "compress":
178
+ if (value === true) options.push("COMPRESS");
179
+ break;
180
+ case "blockSize":
181
+ options.push(`BLOCK_SIZE ${value}`);
182
+ break;
183
+ case "rowGroupSize":
184
+ options.push(`ROW_GROUP_SIZE ${value}`);
185
+ break;
186
+ case "type":
187
+ options.push(`TYPE ${value}`);
188
+ break;
189
+ case "storageVersion":
190
+ options.push(`STORAGE_VERSION '${value}'`);
191
+ break;
192
+ case "encryptionCipher":
193
+ options.push(`ENCRYPTION_CIPHER '${value}'`);
194
+ break;
195
+ case "encryptionKey":
196
+ options.push(`ENCRYPTION_KEY '${value}'`);
197
+ break;
198
+ default:
199
+ }
200
+ if (options.length > 0) parts.push(`(${options.join(", ")})`);
201
+ return parts.filter(Boolean).join(" ");
202
+ };
203
+ };
204
+ //#endregion
205
+ //#region src/manager/database/duck-database-manager.ts
206
+ var DuckDatabaseManager = class {
207
+ #conn;
208
+ #logger;
209
+ constructor(conn, params) {
210
+ this.#conn = conn;
211
+ this.#logger = params?.logger ?? sqlduckDefaultLogtapeLogger.with({ source: "DuckDatabaseManager" });
212
+ }
213
+ /**
214
+ * Attach a database to the current connection
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const dbManager = new DuckDatabaseManager(conn);
219
+ * const database = dbManager.attach({
220
+ * type: 'memory', // can be 'duckdb', 's3'...
221
+ * alias: 'mydb',
222
+ * options: { COMPRESS: 'true' }
223
+ * });
224
+ *
225
+ * console.log(database.alias); // 'mydb'
226
+ * ```
227
+ */
228
+ attach = async (dbParams, options) => {
229
+ const params = duckConnectionParamsZodSchema.parse(dbParams);
230
+ const rawSql = new DuckDatabaseAttachCommand(params, options).getRawSql();
231
+ await this.#executeRawSqlCommand(`attach(${params.alias})`, rawSql);
232
+ return new Database({ alias: params.alias });
233
+ };
234
+ attachOrReplace = async (dbParams) => {
235
+ return this.attach(dbParams, { behaviour: "OR REPLACE" });
236
+ };
237
+ attachIfNotExists = async (dbParams) => {
238
+ return this.attach(dbParams, { behaviour: "IF NOT EXISTS" });
239
+ };
240
+ showDatabases = async () => {
241
+ return await this.#executeRawSqlCommand("showDatabases()", `SHOW DATABASES`);
242
+ };
243
+ detach = async (dbAlias) => {
244
+ assertValidAliasName(dbAlias);
245
+ await this.#executeRawSqlCommand(`detach(${dbAlias})`, `DETACH ${dbAlias}`);
246
+ return true;
247
+ };
248
+ detachIfExists = async (dbAlias) => {
249
+ assertValidAliasName(dbAlias);
250
+ await this.#executeRawSqlCommand(`detachIfExists(${dbAlias})`, `DETACH IF EXISTS ${dbAlias}`);
251
+ return true;
252
+ };
253
+ /**
254
+ * The statistics recomputed by the ANALYZE statement are only used for join order optimization.
255
+ *
256
+ * It is therefore recommended to recompute these statistics for improved join orders,
257
+ * especially after performing large updates (inserts and/or deletes).
258
+ *
259
+ * @link https://duckdb.org/docs/stable/sql/statements/analyze
260
+ */
261
+ analyze = async () => {
262
+ await this.#executeRawSqlCommand("analyze()", "ANALYZE");
263
+ return true;
264
+ };
265
+ checkpoint = async (dbAlias) => {
266
+ const safeAlias = duckValidatorsZod.aliasName.parse(dbAlias);
267
+ await this.#executeRawSqlCommand(`checkpoint(${safeAlias})`, `CHECKPOINT ${safeAlias}`);
268
+ return true;
269
+ };
270
+ vacuum = async () => {
271
+ await this.#executeRawSqlCommand("vacuum()", "VACUUM");
272
+ return true;
273
+ };
274
+ #executeRawSqlCommand = async (name, rawSql) => {
275
+ const startTime = Date.now();
276
+ try {
277
+ const result = await this.#conn.runAndReadAll(rawSql);
278
+ const timeMs = Math.round(Date.now() - startTime);
279
+ const data = result.getRowObjectsJS();
280
+ this.#logger.info(`DuckDatabaseManager.${name} in ${timeMs}ms`, { timeMs });
281
+ return data;
282
+ } catch (e) {
283
+ const msg = `DuckDatabaseManager: failed to run "${name}" - ${e?.message ?? ""}`;
284
+ const timeMs = Math.round(Date.now() - startTime);
285
+ this.#logger.error(msg, {
286
+ name,
287
+ sql: rawSql,
288
+ timeMs
289
+ });
290
+ throw new Error(msg, { cause: e });
291
+ }
292
+ };
293
+ };
294
+ //#endregion
127
295
  //#region src/table/get-duckdb-number-column-type.ts
128
296
  const isFloatValue = (value) => {
129
297
  if (!Number.isFinite(value)) return true;
@@ -157,7 +325,7 @@ const createOptions = {
157
325
  CREATE_OR_REPLACE: "CREATE OR REPLACE TABLE",
158
326
  IF_NOT_EXISTS: "CREATE TABLE IF NOT EXISTS"
159
327
  };
160
- const duckDbTypes = [
328
+ const duckDbTypesMap = new Map([
161
329
  ["VARCHAR", VARCHAR],
162
330
  ["BIGINT", BIGINT],
163
331
  ["TIMESTAMP", TIMESTAMP],
@@ -166,8 +334,7 @@ const duckDbTypes = [
166
334
  ["INTEGER", INTEGER],
167
335
  ["DOUBLE", DOUBLE],
168
336
  ["FLOAT", FLOAT]
169
- ];
170
- const duckDbTypesMap = new Map(duckDbTypes);
337
+ ]);
171
338
  const getTableCreateFromZod = (params) => {
172
339
  const { table, schema, options } = params;
173
340
  const { create = "CREATE" } = options ?? {};
@@ -303,10 +470,10 @@ async function* rowsToColumnsChunks(params) {
303
470
  //#endregion
304
471
  //#region src/sql-duck.ts
305
472
  var SqlDuck = class {
306
- #duck;
473
+ #conn;
307
474
  #logger;
308
475
  constructor(params) {
309
- this.#duck = params.conn;
476
+ this.#conn = params.conn;
310
477
  this.#logger = params.logger ?? sqlduckDefaultLogtapeLogger;
311
478
  }
312
479
  /**
@@ -347,16 +514,16 @@ var SqlDuck = class {
347
514
  * ```
348
515
  */
349
516
  toTable = async (params) => {
350
- const { table, schema, chunkSize = 2048, rowStream, createOptions, onDataAppended } = params;
517
+ const { table, schema, chunkSize = 2048, rowStream, createOptions, onDataAppended, autoCheckpoint = true } = params;
351
518
  if (!Number.isSafeInteger(chunkSize) || chunkSize < 1 || chunkSize > 2048) throw new Error("chunkSize must be a number between 1 and 2048");
352
519
  const timeStart = Date.now();
353
520
  const { columnTypes, ddl } = await createTableFromZod({
354
- conn: this.#duck,
521
+ conn: this.#conn,
355
522
  schema,
356
523
  table,
357
524
  options: createOptions
358
525
  });
359
- const appender = await this.#duck.createAppender(table.tableName, table.schemaName, table.databaseName);
526
+ const appender = await this.#conn.createAppender(table.tableName, table.schemaName, table.databaseName);
360
527
  const chunkTypes = Array.from(columnTypes.values());
361
528
  let totalRows = 0;
362
529
  const dataAppendedCollector = createOnDataAppendedCollector();
@@ -379,6 +546,14 @@ var SqlDuck = class {
379
546
  }
380
547
  }
381
548
  appender.closeSync();
549
+ if (autoCheckpoint && typeof table.databaseName === "string") {
550
+ const dbManager = new DuckDatabaseManager(this.#conn);
551
+ try {
552
+ await dbManager.checkpoint(table.databaseName);
553
+ } catch (e) {
554
+ this.#logger.warning(`Failed to checkpoint database '${table.databaseName}' after appending data into table '${table.getFullName()}' - ${e?.message ?? ""}`, { table: table.getFullName() });
555
+ }
556
+ }
382
557
  const timeMs = Math.round(Date.now() - timeStart);
383
558
  this.#logger.info(`Successfully appended ${totalRows} rows into '${table.getFullName()}' in ${timeMs}ms`, {
384
559
  table: table.getFullName(),
@@ -399,7 +574,19 @@ var SqlDuck = class {
399
574
  };
400
575
  };
401
576
  //#endregion
402
- //#region src/table/table.ts
577
+ //#region src/utils/zod-codecs.ts
578
+ const zodCodecs = {
579
+ dateToString: z.codec(z.date(), z.iso.datetime(), {
580
+ decode: (date) => date.toISOString(),
581
+ encode: (isoString) => new Date(isoString)
582
+ }),
583
+ bigintToString: z.codec(z.bigint(), z.string().meta({ format: "int64" }), {
584
+ decode: (bigint) => bigint.toString(),
585
+ encode: BigInt
586
+ })
587
+ };
588
+ //#endregion
589
+ //#region src/objects/table.ts
403
590
  var Table = class Table {
404
591
  #fqTable;
405
592
  get tableName() {
@@ -441,16 +628,4 @@ var Table = class Table {
441
628
  };
442
629
  };
443
630
  //#endregion
444
- //#region src/utils/zod-codecs.ts
445
- const zodCodecs = {
446
- dateToString: z.codec(z.date(), z.iso.datetime(), {
447
- decode: (date) => date.toISOString(),
448
- encode: (isoString) => new Date(isoString)
449
- }),
450
- bigintToString: z.codec(z.bigint(), z.string().meta({ format: "int64" }), {
451
- decode: (bigint) => bigint.toString(),
452
- encode: BigInt
453
- })
454
- };
455
- //#endregion
456
- export { DuckMemory, SqlDuck, Table, flowbladeLogtapeSqlduckConfig, getTableCreateFromZod, sqlduckDefaultLogtapeLogger, zodCodecs };
631
+ export { Database, DuckDatabaseManager, DuckMemory, SqlDuck, Table, duckReservedKeywords, flowbladeLogtapeSqlduckConfig, getTableCreateFromZod, sqlduckDefaultLogtapeLogger, zodCodecs };