@flowblade/sqlduck 0.0.1
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 +20 -0
- package/dist/index.cjs +182 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.mts +62 -0
- package/dist/index.mjs +158 -0
- package/package.json +107 -0
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## @flowblade/sqlduck
|
|
2
|
+
|
|
3
|
+
> Currently experimental
|
|
4
|
+
|
|
5
|
+
### Quick start
|
|
6
|
+
|
|
7
|
+
### Environment variables
|
|
8
|
+
|
|
9
|
+
### Schema
|
|
10
|
+
|
|
11
|
+
### Local scripts
|
|
12
|
+
|
|
13
|
+
| Name | Description |
|
|
14
|
+
| ----------------- | ------------------------------ |
|
|
15
|
+
| `yarn build` | |
|
|
16
|
+
| `yarn typecheck` | |
|
|
17
|
+
| `yarn lint` | Check for lint errors |
|
|
18
|
+
| `yarn lint --fix` | Attempt to run linter auto-fix |
|
|
19
|
+
| `yarn test-unit` | Run unit tests |
|
|
20
|
+
| `yarn test-e2e` | Run unit tests |
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __copyProps = (to, from, except, desc) => {
|
|
8
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
9
|
+
key = keys[i];
|
|
10
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
11
|
+
get: ((k) => from[k]).bind(null, key),
|
|
12
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
18
|
+
value: mod,
|
|
19
|
+
enumerable: true
|
|
20
|
+
}) : target, mod));
|
|
21
|
+
let _duckdb_node_api = require("@duckdb/node-api");
|
|
22
|
+
let zod = require("zod");
|
|
23
|
+
zod = __toESM(zod);
|
|
24
|
+
const toDuckValue = (value) => {
|
|
25
|
+
if (value instanceof Date) return new _duckdb_node_api.DuckDBTimestampValue(BigInt(value.getTime() * 1e3));
|
|
26
|
+
return value === void 0 ? null : value;
|
|
27
|
+
};
|
|
28
|
+
async function* rowsToColumnsChunks(rows, chunkSize) {
|
|
29
|
+
if (!Number.isSafeInteger(chunkSize) || chunkSize <= 0) throw new Error(`chunkSize must be a positive integer, got ${chunkSize}`);
|
|
30
|
+
const first = await rows.next();
|
|
31
|
+
if (first.done) return;
|
|
32
|
+
const keys = Object.keys(first.value);
|
|
33
|
+
let columns = keys.map(() => []);
|
|
34
|
+
let rowsInChunk = 0;
|
|
35
|
+
keys.forEach((k, i) => columns[i].push(toDuckValue(first.value[k])));
|
|
36
|
+
rowsInChunk++;
|
|
37
|
+
if (rowsInChunk >= chunkSize) {
|
|
38
|
+
yield columns;
|
|
39
|
+
columns = keys.map(() => []);
|
|
40
|
+
rowsInChunk = 0;
|
|
41
|
+
}
|
|
42
|
+
for await (const row of rows) {
|
|
43
|
+
keys.forEach((k, i) => columns[i].push(toDuckValue(row[k])));
|
|
44
|
+
rowsInChunk++;
|
|
45
|
+
if (rowsInChunk >= chunkSize) {
|
|
46
|
+
yield columns;
|
|
47
|
+
columns = keys.map(() => []);
|
|
48
|
+
rowsInChunk = 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (rowsInChunk > 0) yield columns;
|
|
52
|
+
}
|
|
53
|
+
const getTableCreateFromZod = (table, schema) => {
|
|
54
|
+
const fqTable = table.getFullyQualifiedTableName();
|
|
55
|
+
const json = schema.toJSONSchema({ target: "openapi-3.0" });
|
|
56
|
+
const columns = [];
|
|
57
|
+
if (json.properties === void 0) throw new TypeError("Schema must have at least one property");
|
|
58
|
+
const columnTypes = [];
|
|
59
|
+
for (const [columnName, def] of Object.entries(json.properties)) {
|
|
60
|
+
const { type, nullable, format, primaryKey } = def;
|
|
61
|
+
const c = { name: columnName };
|
|
62
|
+
switch (type) {
|
|
63
|
+
case "string":
|
|
64
|
+
switch (format) {
|
|
65
|
+
case "date-time":
|
|
66
|
+
c.duckdbType = _duckdb_node_api.TIMESTAMP;
|
|
67
|
+
break;
|
|
68
|
+
case "int64":
|
|
69
|
+
c.duckdbType = _duckdb_node_api.BIGINT;
|
|
70
|
+
break;
|
|
71
|
+
default: c.duckdbType = _duckdb_node_api.VARCHAR;
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
case "number":
|
|
75
|
+
c.duckdbType = _duckdb_node_api.INTEGER;
|
|
76
|
+
break;
|
|
77
|
+
default: throw new Error("Not a supported type");
|
|
78
|
+
}
|
|
79
|
+
if (primaryKey === true) c.constraint = "PRIMARY KEY";
|
|
80
|
+
else if (nullable !== true) c.constraint = "NOT NULL";
|
|
81
|
+
columnTypes.push([columnName, c.duckdbType]);
|
|
82
|
+
columns.push(c);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
ddl: [
|
|
86
|
+
`CREATE OR REPLACE TABLE ${fqTable} (\n`,
|
|
87
|
+
columns.map((colDDL) => {
|
|
88
|
+
const { name, duckdbType, constraint } = colDDL;
|
|
89
|
+
return ` ${[
|
|
90
|
+
name,
|
|
91
|
+
duckdbType.toString(),
|
|
92
|
+
constraint
|
|
93
|
+
].filter(Boolean).join(" ")}`;
|
|
94
|
+
}).join(",\n"),
|
|
95
|
+
"\n)"
|
|
96
|
+
].join(""),
|
|
97
|
+
columnTypes
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const createTableFromZod = async (params) => {
|
|
101
|
+
const { conn, table, schema } = params;
|
|
102
|
+
const { ddl, columnTypes } = getTableCreateFromZod(table, schema);
|
|
103
|
+
try {
|
|
104
|
+
await conn.run(ddl);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
throw new Error(`Failed to create table '${table.getFullyQualifiedTableName()}': ${e.message}`, { cause: e });
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
ddl,
|
|
110
|
+
columnTypes
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
var SqlDuck = class {
|
|
114
|
+
#duck;
|
|
115
|
+
#logger;
|
|
116
|
+
constructor(params) {
|
|
117
|
+
this.#duck = params.conn;
|
|
118
|
+
this.#logger = params.logger;
|
|
119
|
+
}
|
|
120
|
+
toTable = async (params) => {
|
|
121
|
+
const { table, schema, chunkSize, rowStream } = params;
|
|
122
|
+
const { columnTypes } = await createTableFromZod({
|
|
123
|
+
conn: this.#duck,
|
|
124
|
+
schema,
|
|
125
|
+
table
|
|
126
|
+
});
|
|
127
|
+
const appender = await this.#duck.createAppender(table.fqTable.name, table.fqTable.schema, table.fqTable.database);
|
|
128
|
+
const chunkTypes = columnTypes.map((v) => v[1]);
|
|
129
|
+
const columnStream = rowsToColumnsChunks(rowStream, chunkSize ?? 2048);
|
|
130
|
+
for await (const dataChunk of columnStream) {
|
|
131
|
+
const chunk = _duckdb_node_api.DuckDBDataChunk.create(chunkTypes);
|
|
132
|
+
if (this.#logger) this.#logger(`Inserting chunk of ${dataChunk.length} rows`);
|
|
133
|
+
chunk.setColumns(dataChunk);
|
|
134
|
+
appender.appendDataChunk(chunk);
|
|
135
|
+
appender.flushSync();
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
var Table = class Table {
|
|
140
|
+
#fqTable;
|
|
141
|
+
get fqTable() {
|
|
142
|
+
return this.#fqTable;
|
|
143
|
+
}
|
|
144
|
+
constructor(fqTableOrName) {
|
|
145
|
+
this.#fqTable = typeof fqTableOrName === "string" ? { name: fqTableOrName } : fqTableOrName;
|
|
146
|
+
}
|
|
147
|
+
getFullyQualifiedTableName = (options) => {
|
|
148
|
+
const { defaultDatabase, defaultSchema } = options ?? {};
|
|
149
|
+
const { name, database = defaultDatabase, schema = defaultSchema } = this.#fqTable;
|
|
150
|
+
return [
|
|
151
|
+
database,
|
|
152
|
+
schema,
|
|
153
|
+
name
|
|
154
|
+
].filter(Boolean).join(".");
|
|
155
|
+
};
|
|
156
|
+
withDatabase = (database) => {
|
|
157
|
+
return new Table({
|
|
158
|
+
...this.#fqTable,
|
|
159
|
+
database
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
withSchema = (schema) => {
|
|
163
|
+
return new Table({
|
|
164
|
+
...this.#fqTable,
|
|
165
|
+
schema
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
const zodCodecs = {
|
|
170
|
+
dateToString: zod.codec(zod.date(), zod.iso.datetime(), {
|
|
171
|
+
decode: (date) => date.toISOString(),
|
|
172
|
+
encode: (isoString) => new Date(isoString)
|
|
173
|
+
}),
|
|
174
|
+
bigintToString: zod.codec(zod.bigint(), zod.string().meta({ format: "int64" }), {
|
|
175
|
+
decode: (bigint) => bigint.toString(),
|
|
176
|
+
encode: BigInt
|
|
177
|
+
})
|
|
178
|
+
};
|
|
179
|
+
exports.SqlDuck = SqlDuck;
|
|
180
|
+
exports.Table = Table;
|
|
181
|
+
exports.getTableCreateFromZod = getTableCreateFromZod;
|
|
182
|
+
exports.zodCodecs = zodCodecs;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DuckDBConnection, DuckDBType } from "@duckdb/node-api";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { ZodObject } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/table/table.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Fully qualified table information
|
|
8
|
+
*/
|
|
9
|
+
type FQTable = {
|
|
10
|
+
name: string;
|
|
11
|
+
schema?: string;
|
|
12
|
+
database?: string;
|
|
13
|
+
};
|
|
14
|
+
declare class Table {
|
|
15
|
+
#private;
|
|
16
|
+
get fqTable(): FQTable;
|
|
17
|
+
constructor(fqTableOrName: FQTable | string);
|
|
18
|
+
/**
|
|
19
|
+
* Return fully qualified table name by concatenating
|
|
20
|
+
* database, schema and table with a 'dot' separator.
|
|
21
|
+
*/
|
|
22
|
+
getFullyQualifiedTableName: (options?: {
|
|
23
|
+
defaultDatabase?: string;
|
|
24
|
+
defaultSchema?: string;
|
|
25
|
+
}) => string;
|
|
26
|
+
withDatabase: (database: string) => Table;
|
|
27
|
+
withSchema: (schema: string) => Table;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/table/table-schema-zod.type.d.ts
|
|
31
|
+
type TableSchemaZod = ZodObject;
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/sql-duck.d.ts
|
|
34
|
+
type SqlDuckParams = {
|
|
35
|
+
conn: DuckDBConnection;
|
|
36
|
+
logger?: (msg: string) => void;
|
|
37
|
+
};
|
|
38
|
+
type ToTableParams<TSchema extends TableSchemaZod> = {
|
|
39
|
+
table: Table;
|
|
40
|
+
schema: TSchema;
|
|
41
|
+
rowStream: AsyncIterableIterator<z.infer<TSchema>>;
|
|
42
|
+
chunkSize?: number;
|
|
43
|
+
};
|
|
44
|
+
declare class SqlDuck {
|
|
45
|
+
#private;
|
|
46
|
+
constructor(params: SqlDuckParams);
|
|
47
|
+
toTable: <TSchema extends ZodObject>(params: ToTableParams<TSchema>) => Promise<undefined>;
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/table/get-table-create-from-zod.d.ts
|
|
51
|
+
declare const getTableCreateFromZod: <T extends ZodObject>(table: Table, schema: T) => {
|
|
52
|
+
ddl: string;
|
|
53
|
+
columnTypes: [name: string, type: DuckDBType][];
|
|
54
|
+
};
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/utils/zod-codecs.d.ts
|
|
57
|
+
declare const zodCodecs: {
|
|
58
|
+
readonly dateToString: z.ZodCodec<z.ZodDate, z.ZodISODateTime>;
|
|
59
|
+
readonly bigintToString: z.ZodCodec<z.ZodBigInt, z.ZodString>;
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { SqlDuck, type SqlDuckParams, Table, getTableCreateFromZod, zodCodecs };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DuckDBConnection, DuckDBType } from "@duckdb/node-api";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { ZodObject } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/table/table.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Fully qualified table information
|
|
8
|
+
*/
|
|
9
|
+
type FQTable = {
|
|
10
|
+
name: string;
|
|
11
|
+
schema?: string;
|
|
12
|
+
database?: string;
|
|
13
|
+
};
|
|
14
|
+
declare class Table {
|
|
15
|
+
#private;
|
|
16
|
+
get fqTable(): FQTable;
|
|
17
|
+
constructor(fqTableOrName: FQTable | string);
|
|
18
|
+
/**
|
|
19
|
+
* Return fully qualified table name by concatenating
|
|
20
|
+
* database, schema and table with a 'dot' separator.
|
|
21
|
+
*/
|
|
22
|
+
getFullyQualifiedTableName: (options?: {
|
|
23
|
+
defaultDatabase?: string;
|
|
24
|
+
defaultSchema?: string;
|
|
25
|
+
}) => string;
|
|
26
|
+
withDatabase: (database: string) => Table;
|
|
27
|
+
withSchema: (schema: string) => Table;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/table/table-schema-zod.type.d.ts
|
|
31
|
+
type TableSchemaZod = ZodObject;
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/sql-duck.d.ts
|
|
34
|
+
type SqlDuckParams = {
|
|
35
|
+
conn: DuckDBConnection;
|
|
36
|
+
logger?: (msg: string) => void;
|
|
37
|
+
};
|
|
38
|
+
type ToTableParams<TSchema extends TableSchemaZod> = {
|
|
39
|
+
table: Table;
|
|
40
|
+
schema: TSchema;
|
|
41
|
+
rowStream: AsyncIterableIterator<z.infer<TSchema>>;
|
|
42
|
+
chunkSize?: number;
|
|
43
|
+
};
|
|
44
|
+
declare class SqlDuck {
|
|
45
|
+
#private;
|
|
46
|
+
constructor(params: SqlDuckParams);
|
|
47
|
+
toTable: <TSchema extends ZodObject>(params: ToTableParams<TSchema>) => Promise<undefined>;
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/table/get-table-create-from-zod.d.ts
|
|
51
|
+
declare const getTableCreateFromZod: <T extends ZodObject>(table: Table, schema: T) => {
|
|
52
|
+
ddl: string;
|
|
53
|
+
columnTypes: [name: string, type: DuckDBType][];
|
|
54
|
+
};
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/utils/zod-codecs.d.ts
|
|
57
|
+
declare const zodCodecs: {
|
|
58
|
+
readonly dateToString: z.ZodCodec<z.ZodDate, z.ZodISODateTime>;
|
|
59
|
+
readonly bigintToString: z.ZodCodec<z.ZodBigInt, z.ZodString>;
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { SqlDuck, type SqlDuckParams, Table, getTableCreateFromZod, zodCodecs };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { BIGINT, DuckDBDataChunk, DuckDBTimestampValue, INTEGER, TIMESTAMP, VARCHAR } from "@duckdb/node-api";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
const toDuckValue = (value) => {
|
|
4
|
+
if (value instanceof Date) return new DuckDBTimestampValue(BigInt(value.getTime() * 1e3));
|
|
5
|
+
return value === void 0 ? null : value;
|
|
6
|
+
};
|
|
7
|
+
async function* rowsToColumnsChunks(rows, chunkSize) {
|
|
8
|
+
if (!Number.isSafeInteger(chunkSize) || chunkSize <= 0) throw new Error(`chunkSize must be a positive integer, got ${chunkSize}`);
|
|
9
|
+
const first = await rows.next();
|
|
10
|
+
if (first.done) return;
|
|
11
|
+
const keys = Object.keys(first.value);
|
|
12
|
+
let columns = keys.map(() => []);
|
|
13
|
+
let rowsInChunk = 0;
|
|
14
|
+
keys.forEach((k, i) => columns[i].push(toDuckValue(first.value[k])));
|
|
15
|
+
rowsInChunk++;
|
|
16
|
+
if (rowsInChunk >= chunkSize) {
|
|
17
|
+
yield columns;
|
|
18
|
+
columns = keys.map(() => []);
|
|
19
|
+
rowsInChunk = 0;
|
|
20
|
+
}
|
|
21
|
+
for await (const row of rows) {
|
|
22
|
+
keys.forEach((k, i) => columns[i].push(toDuckValue(row[k])));
|
|
23
|
+
rowsInChunk++;
|
|
24
|
+
if (rowsInChunk >= chunkSize) {
|
|
25
|
+
yield columns;
|
|
26
|
+
columns = keys.map(() => []);
|
|
27
|
+
rowsInChunk = 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (rowsInChunk > 0) yield columns;
|
|
31
|
+
}
|
|
32
|
+
const getTableCreateFromZod = (table, schema) => {
|
|
33
|
+
const fqTable = table.getFullyQualifiedTableName();
|
|
34
|
+
const json = schema.toJSONSchema({ target: "openapi-3.0" });
|
|
35
|
+
const columns = [];
|
|
36
|
+
if (json.properties === void 0) throw new TypeError("Schema must have at least one property");
|
|
37
|
+
const columnTypes = [];
|
|
38
|
+
for (const [columnName, def] of Object.entries(json.properties)) {
|
|
39
|
+
const { type, nullable, format, primaryKey } = def;
|
|
40
|
+
const c = { name: columnName };
|
|
41
|
+
switch (type) {
|
|
42
|
+
case "string":
|
|
43
|
+
switch (format) {
|
|
44
|
+
case "date-time":
|
|
45
|
+
c.duckdbType = TIMESTAMP;
|
|
46
|
+
break;
|
|
47
|
+
case "int64":
|
|
48
|
+
c.duckdbType = BIGINT;
|
|
49
|
+
break;
|
|
50
|
+
default: c.duckdbType = VARCHAR;
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
case "number":
|
|
54
|
+
c.duckdbType = INTEGER;
|
|
55
|
+
break;
|
|
56
|
+
default: throw new Error("Not a supported type");
|
|
57
|
+
}
|
|
58
|
+
if (primaryKey === true) c.constraint = "PRIMARY KEY";
|
|
59
|
+
else if (nullable !== true) c.constraint = "NOT NULL";
|
|
60
|
+
columnTypes.push([columnName, c.duckdbType]);
|
|
61
|
+
columns.push(c);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
ddl: [
|
|
65
|
+
`CREATE OR REPLACE TABLE ${fqTable} (\n`,
|
|
66
|
+
columns.map((colDDL) => {
|
|
67
|
+
const { name, duckdbType, constraint } = colDDL;
|
|
68
|
+
return ` ${[
|
|
69
|
+
name,
|
|
70
|
+
duckdbType.toString(),
|
|
71
|
+
constraint
|
|
72
|
+
].filter(Boolean).join(" ")}`;
|
|
73
|
+
}).join(",\n"),
|
|
74
|
+
"\n)"
|
|
75
|
+
].join(""),
|
|
76
|
+
columnTypes
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
const createTableFromZod = async (params) => {
|
|
80
|
+
const { conn, table, schema } = params;
|
|
81
|
+
const { ddl, columnTypes } = getTableCreateFromZod(table, schema);
|
|
82
|
+
try {
|
|
83
|
+
await conn.run(ddl);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
throw new Error(`Failed to create table '${table.getFullyQualifiedTableName()}': ${e.message}`, { cause: e });
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
ddl,
|
|
89
|
+
columnTypes
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
var SqlDuck = class {
|
|
93
|
+
#duck;
|
|
94
|
+
#logger;
|
|
95
|
+
constructor(params) {
|
|
96
|
+
this.#duck = params.conn;
|
|
97
|
+
this.#logger = params.logger;
|
|
98
|
+
}
|
|
99
|
+
toTable = async (params) => {
|
|
100
|
+
const { table, schema, chunkSize, rowStream } = params;
|
|
101
|
+
const { columnTypes } = await createTableFromZod({
|
|
102
|
+
conn: this.#duck,
|
|
103
|
+
schema,
|
|
104
|
+
table
|
|
105
|
+
});
|
|
106
|
+
const appender = await this.#duck.createAppender(table.fqTable.name, table.fqTable.schema, table.fqTable.database);
|
|
107
|
+
const chunkTypes = columnTypes.map((v) => v[1]);
|
|
108
|
+
const columnStream = rowsToColumnsChunks(rowStream, chunkSize ?? 2048);
|
|
109
|
+
for await (const dataChunk of columnStream) {
|
|
110
|
+
const chunk = DuckDBDataChunk.create(chunkTypes);
|
|
111
|
+
if (this.#logger) this.#logger(`Inserting chunk of ${dataChunk.length} rows`);
|
|
112
|
+
chunk.setColumns(dataChunk);
|
|
113
|
+
appender.appendDataChunk(chunk);
|
|
114
|
+
appender.flushSync();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
var Table = class Table {
|
|
119
|
+
#fqTable;
|
|
120
|
+
get fqTable() {
|
|
121
|
+
return this.#fqTable;
|
|
122
|
+
}
|
|
123
|
+
constructor(fqTableOrName) {
|
|
124
|
+
this.#fqTable = typeof fqTableOrName === "string" ? { name: fqTableOrName } : fqTableOrName;
|
|
125
|
+
}
|
|
126
|
+
getFullyQualifiedTableName = (options) => {
|
|
127
|
+
const { defaultDatabase, defaultSchema } = options ?? {};
|
|
128
|
+
const { name, database = defaultDatabase, schema = defaultSchema } = this.#fqTable;
|
|
129
|
+
return [
|
|
130
|
+
database,
|
|
131
|
+
schema,
|
|
132
|
+
name
|
|
133
|
+
].filter(Boolean).join(".");
|
|
134
|
+
};
|
|
135
|
+
withDatabase = (database) => {
|
|
136
|
+
return new Table({
|
|
137
|
+
...this.#fqTable,
|
|
138
|
+
database
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
withSchema = (schema) => {
|
|
142
|
+
return new Table({
|
|
143
|
+
...this.#fqTable,
|
|
144
|
+
schema
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
const zodCodecs = {
|
|
149
|
+
dateToString: z.codec(z.date(), z.iso.datetime(), {
|
|
150
|
+
decode: (date) => date.toISOString(),
|
|
151
|
+
encode: (isoString) => new Date(isoString)
|
|
152
|
+
}),
|
|
153
|
+
bigintToString: z.codec(z.bigint(), z.string().meta({ format: "int64" }), {
|
|
154
|
+
decode: (bigint) => bigint.toString(),
|
|
155
|
+
encode: BigInt
|
|
156
|
+
})
|
|
157
|
+
};
|
|
158
|
+
export { SqlDuck, Table, getTableCreateFromZod, zodCodecs };
|
package/package.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flowblade/sqlduck",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": {
|
|
9
|
+
"types": "./dist/index.d.mts",
|
|
10
|
+
"default": "./dist/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"require": {
|
|
13
|
+
"types": "./dist/index.d.cts",
|
|
14
|
+
"default": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"author": {
|
|
20
|
+
"name": "Vanvelthem Sébastien",
|
|
21
|
+
"url": "https://github.com/belgattitude"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"homepage": "https://github.com/belgattitude/flowblade",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/belgattitude/flowblade.git",
|
|
28
|
+
"directory": "packages/sqlduck"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"clean": "rimraf ./dist ./coverage ./tsconfig.tsbuildinfo",
|
|
32
|
+
"build": "tsdown",
|
|
33
|
+
"build-release": "yarn build && rimraf ./_release && yarn pack && mkdir ./_release && tar zxvf ./package.tgz --directory ./_release && rm ./package.tgz",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test-unit": "vitest run",
|
|
36
|
+
"test-unit-watch": "vitest --ui",
|
|
37
|
+
"test-e2e": "vitest -c vitest.e2e.config.ts run",
|
|
38
|
+
"test-e2e-watch": "vitest -c vitest.e2e.config.ts --ui",
|
|
39
|
+
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
40
|
+
"lint": "eslint . --ext .ts,.tsx,.js,.jsx,.mjs,.cjs,.mts,.cts --cache --cache-location ../../../.cache/eslint/sqlduck.eslintcache",
|
|
41
|
+
"fix-staged": "lint-staged --allow-empty",
|
|
42
|
+
"check-dist": "es-check --config=.escheckrc.json",
|
|
43
|
+
"check-pub": "publint",
|
|
44
|
+
"check-size": "size-limit"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@flowblade/core": "workspace:^",
|
|
48
|
+
"@flowblade/source-duckdb": "workspace:^",
|
|
49
|
+
"@flowblade/sql-tag": "workspace:^",
|
|
50
|
+
"@standard-schema/spec": "^1.1.0",
|
|
51
|
+
"p-mutex": "^1.0.0",
|
|
52
|
+
"valibot": "^1.2.0",
|
|
53
|
+
"zod": "^4.2.1"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@duckdb/node-api": "^1.4.3-r.1"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@belgattitude/eslint-config-bases": "8.8.0",
|
|
60
|
+
"@dotenvx/dotenvx": "1.51.2",
|
|
61
|
+
"@duckdb/node-api": "1.4.3-r.1",
|
|
62
|
+
"@faker-js/faker": "10.1.0",
|
|
63
|
+
"@flowblade/source-kysely": "workspace:^",
|
|
64
|
+
"@httpx/assert": "0.16.7",
|
|
65
|
+
"@size-limit/esbuild": "12.0.0",
|
|
66
|
+
"@size-limit/file": "12.0.0",
|
|
67
|
+
"@testcontainers/mssqlserver": "11.10.0",
|
|
68
|
+
"@total-typescript/ts-reset": "0.6.1",
|
|
69
|
+
"@traversable/zod": "0.0.57",
|
|
70
|
+
"@types/node": "25.0.3",
|
|
71
|
+
"@typescript-eslint/eslint-plugin": "8.50.1",
|
|
72
|
+
"@typescript-eslint/parser": "8.50.1",
|
|
73
|
+
"@vitest/coverage-v8": "4.0.16",
|
|
74
|
+
"@vitest/ui": "4.0.16",
|
|
75
|
+
"ansis": "4.2.0",
|
|
76
|
+
"browserslist-to-esbuild": "2.1.1",
|
|
77
|
+
"core-js": "3.47.0",
|
|
78
|
+
"cross-env": "10.1.0",
|
|
79
|
+
"es-check": "9.5.3",
|
|
80
|
+
"esbuild": "0.27.2",
|
|
81
|
+
"eslint": "8.57.1",
|
|
82
|
+
"execa": "9.6.1",
|
|
83
|
+
"is-in-ci": "2.0.0",
|
|
84
|
+
"kysely": "0.28.9",
|
|
85
|
+
"npm-run-all2": "8.0.4",
|
|
86
|
+
"prettier": "3.7.4",
|
|
87
|
+
"publint": "0.3.16",
|
|
88
|
+
"regexp.escape": "2.0.1",
|
|
89
|
+
"rimraf": "6.1.2",
|
|
90
|
+
"size-limit": "12.0.0",
|
|
91
|
+
"sql-formatter": "15.6.12",
|
|
92
|
+
"tarn": "3.0.2",
|
|
93
|
+
"tedious": "19.1.3",
|
|
94
|
+
"testcontainers": "11.10.0",
|
|
95
|
+
"tsdown": "0.18.2",
|
|
96
|
+
"tsx": "4.21.0",
|
|
97
|
+
"typescript": "5.9.3",
|
|
98
|
+
"vite-tsconfig-paths": "6.0.3",
|
|
99
|
+
"vitest": "4.0.16"
|
|
100
|
+
},
|
|
101
|
+
"files": [
|
|
102
|
+
"dist"
|
|
103
|
+
],
|
|
104
|
+
"publishConfig": {
|
|
105
|
+
"directory": "_release/package"
|
|
106
|
+
}
|
|
107
|
+
}
|