Skip to content

Instantly share code, notes, and snippets.

@mootari
Last active September 14, 2026 14:05
Show Gist options
  • Select an option

  • Save mootari/da43ddcec9b306b192a3e1ded7bb4ac0 to your computer and use it in GitHub Desktop.

Select an option

Save mootari/da43ddcec9b306b192a3e1ded7bb4ac0 to your computer and use it in GitHub Desktop.
Scrappy DuckDB type layer
import * as d from "@duckdb/node-api";
import type * as D from "@duckdb/node-api";
// DECIMAL covers the parameterized case
export const t = {
BOOLEAN: d.DuckDBBooleanType.create,
USMALLINT: d.DuckDBUSmallIntType.create,
UINTEGER: d.DuckDBUIntegerType.create,
VARCHAR: d.DuckDBVarCharType.create,
TIMESTAMP_MS: d.DuckDBTimestampMillisecondsType.create,
DECIMAL: d.DECIMAL // No static .create on type class
} satisfies {[k: string]: (...a: any[]) => D.DuckDBType};
type ColumnDef = {type: D.DuckDBType, notNull?: true, primaryKey?: true};
type NonNullable = {notNull: true} | {primaryKey: true};
type JSType<T extends D.DuckDBType> = D.JSTypeForTypeId[T["typeId"]];
type ColumnType<C extends ColumnDef, T = JSType<C["type"]>> = C extends NonNullable ? T : T | null;
export type TableDef = {[P: string]: ColumnDef};
export type RowType<T extends TableSchema<TableDef>> = {[K in keyof T["fields"]]: ColumnType<T["fields"][K]>};
export class TableSchema<F extends TableDef> {
readonly fields: F;
constructor(fields: F) {
this.fields = fields;
}
ddl() {
return Object.entries(this.fields)
.map(([name, {type, primaryKey, notNull}]) => [
name,
type,
primaryKey ? "PRIMARY KEY" : "",
notNull ? "NOT NULL": ""
].join(" "))
.join(",\n");
}
append(appender: D.DuckDBAppender, rows: RowType<this>[]) {
const writer = d.DuckDBDataChunkWriter.forAppender(appender, {
converter: d.JSToDuckDBValueConverter,
});
const entries = Object.entries(this.fields);
for(const row of rows) writer.appendRow(entries.map(([k]) => row[k]));
}
}
import type { RowType } from "./duckdb.ts";
import { t, TableSchema } from "./duckdb.ts";
export const stageSchema = new TableSchema({
id: {type: t.VARCHAR(), notNull: true},
changes_time: {type: t.TIMESTAMP_MS(), notNull: true},
changes_seq: {type: t.UINTEGER(), notNull: true},
changes_rev: {type: t.VARCHAR(), notNull: true},
changes_rev_num: {type: t.UINTEGER(), notNull: true},
changes_deleted: {type: t.BOOLEAN(), notNull: true},
});
export type StageRow = RowType<typeof stageSchema>;
import { stageSchema } from './schemas.ts';
// ...
const conn = await db.connect(); // DuckDBInstance
await conn.run(`CREATE TEMP TABLE "stage" (${stageSchema.ddl()})`);
const appender = await conn.createAppender("stage");
// ...
stageSchema.append(appender, rows);
appender.flushSync();
// ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment