This commit is contained in:
Benjamin Toby 2026-04-10 21:13:11 +01:00
parent 5799a2586e
commit e6af7de865
14 changed files with 176 additions and 65 deletions

View File

@ -8,6 +8,8 @@ import _ from "lodash";
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
import chalk from "chalk";
import { writeLiveSchema } from "../functions/live-schema";
import grabDBDir from "../utils/grab-db-dir";
import { cpSync } from "fs";
export default function () {
return new Command("schema")
.description("Build DB From Schema")
@ -16,26 +18,37 @@ export default function () {
.action(async (opts) => {
console.log(`Starting process ...`);
const { config, dbSchema } = await init();
const { ROOT_DIR } = grabDirNames();
const isVector = Boolean(opts.vector || opts.v);
const isTypeDef = Boolean(opts.typedef || opts.t);
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
const manager = new SQLiteSchemaManager({
schema: finaldbSchema,
recreate_vector_table: isVector,
});
await manager.syncSchema();
manager.close();
if (isTypeDef && config.typedef_file_path) {
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
dbSchemaToTypeDef({
dbSchema: finaldbSchema,
dst_file: out_file,
config,
const { ROOT_DIR, BUN_SQLITE_TEMP_DB_FILE_PATH } = grabDirNames();
const { db_file_path } = grabDBDir({ config });
cpSync(db_file_path, BUN_SQLITE_TEMP_DB_FILE_PATH);
try {
const isVector = Boolean(opts.vector || opts.v);
const isTypeDef = Boolean(opts.typedef || opts.t);
const finaldbSchema = appendDefaultFieldsToDbSchema({
dbSchema,
});
const manager = new SQLiteSchemaManager({
schema: finaldbSchema,
recreate_vector_table: isVector,
});
await manager.syncSchema();
manager.close();
if (isTypeDef && config.typedef_file_path) {
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
dbSchemaToTypeDef({
dbSchema: finaldbSchema,
dst_file: out_file,
config,
});
}
writeLiveSchema({ schema: finaldbSchema });
console.log(`${chalk.bold(chalk.green(`DB Schema setup success!`))}`);
process.exit();
}
catch (error) {
console.log(error);
cpSync(BUN_SQLITE_TEMP_DB_FILE_PATH, db_file_path);
process.exit(1);
}
writeLiveSchema({ schema: finaldbSchema });
console.log(`${chalk.bold(chalk.green(`DB Schema setup success!`))}`);
process.exit();
});
}

View File

@ -7,5 +7,6 @@ export default function grabDirNames(params?: Params): {
BUN_SQLITE_DIR: string;
BUN_SQLITE_TEMP_DIR: string;
BUN_SQLITE_LIVE_SCHEMA: string;
BUN_SQLITE_TEMP_DB_FILE_PATH: string;
};
export {};

View File

@ -3,11 +3,13 @@ export default function grabDirNames(params) {
const ROOT_DIR = process.cwd();
const BUN_SQLITE_DIR = path.join(ROOT_DIR, ".bun-sqlite");
const BUN_SQLITE_TEMP_DIR = path.join(BUN_SQLITE_DIR, ".tmp");
const BUN_SQLITE_TEMP_DB_FILE_PATH = path.join(BUN_SQLITE_TEMP_DIR, "temp.db");
const BUN_SQLITE_LIVE_SCHEMA = path.join(BUN_SQLITE_DIR, "live-schema.json");
return {
ROOT_DIR,
BUN_SQLITE_DIR,
BUN_SQLITE_TEMP_DIR,
BUN_SQLITE_LIVE_SCHEMA,
BUN_SQLITE_TEMP_DB_FILE_PATH,
};
}

View File

@ -0,0 +1,7 @@
type Params = {
sql: string;
table: string;
data: any[];
};
export default function ({ sql: passed_sql, table, data }: Params): Promise<string>;
export {};

View File

@ -0,0 +1,23 @@
import init from "../functions/init";
export default async function ({ sql: passed_sql, table, data }) {
let sql = passed_sql;
const { dbSchema } = await init();
const table_schema = dbSchema.tables.find((t) => t.tableName == table);
if (table_schema?.tableName) {
const set_sql = Object.keys(data[0])
.map((field) => `${field} = excluded.${field}`)
.join(", ");
const unique_fields = table_schema.fields.filter((f) => f.unique);
for (let i = 0; i < unique_fields.length; i++) {
const field = unique_fields[i];
sql += ` ON CONFLICT(${field?.fieldName}) DO UPDATE SET ${set_sql}`;
}
if (table_schema.uniqueConstraints?.[0]) {
for (let i = 0; i < table_schema.uniqueConstraints.length; i++) {
const constraint = table_schema.uniqueConstraints[i];
sql += ` ON CONFLICT(${constraint?.constraintTableFields?.map((c) => c.value)?.join(", ")}) DO UPDATE SET ${set_sql}`;
}
}
}
return sql;
}

View File

@ -6,10 +6,11 @@ type Params<Schema extends {
}, Table extends string = string> = {
table: Table;
data: Schema[];
update_on_duplicate?: boolean;
};
export default function DbInsert<Schema extends {
[k: string]: any;
} = {
[k: string]: any;
}, Table extends string = string>({ table, data }: Params<Schema, Table>): Promise<APIResponseObject>;
}, Table extends string = string>({ table, data, update_on_duplicate, }: Params<Schema, Table>): Promise<APIResponseObject>;
export {};

View File

@ -1,6 +1,7 @@
import DbClient from ".";
import sqlInsertGenerator from "../../utils/sql-insert-generator";
export default async function DbInsert({ table, data }) {
import grabDuplicateSafeInsertSql from "../grab-duplicate-safe-insert-sql";
export default async function DbInsert({ table, data, update_on_duplicate, }) {
let sqlObj = null;
try {
const finalData = data.map((d) => ({
@ -13,7 +14,12 @@ export default async function DbInsert({ table, data }) {
tableName: table,
data: finalData,
}) || null;
const res = DbClient.run(sqlObj?.query || "", sqlObj?.values || []);
let sql = sqlObj?.query || "";
if (update_on_duplicate && data[0]) {
sql = await grabDuplicateSafeInsertSql({ data, table, sql });
}
(sqlObj || {}).query = sql;
const res = DbClient.run(sql, sqlObj?.values || []);
return {
success: Boolean(Number(res.lastInsertRowid)),
postInsertReturn: {

View File

@ -290,12 +290,7 @@ class SQLiteSchemaManager {
const parts = [fieldName];
// Data type mapping
const dataType = this.mapDataType(field);
if (dataType == "BLOB") {
parts.push("FLOAT[128]");
}
else {
parts.push(dataType);
}
parts.push(dataType);
// Primary key
if (field.primaryKey) {
parts.push("PRIMARY KEY");

View File

@ -1,6 +1,6 @@
{
"name": "@moduletrace/bun-sqlite",
"version": "1.0.27",
"version": "1.0.28",
"description": "SQLite manager for Bun",
"author": "Benjamin Toby",
"main": "dist/index.js",

View File

@ -8,6 +8,8 @@ import _ from "lodash";
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
import chalk from "chalk";
import { writeLiveSchema } from "../functions/live-schema";
import grabDBDir from "../utils/grab-db-dir";
import { cpSync } from "fs";
export default function () {
return new Command("schema")
@ -21,39 +23,50 @@ export default function () {
console.log(`Starting process ...`);
const { config, dbSchema } = await init();
const { ROOT_DIR } = grabDirNames();
const { ROOT_DIR, BUN_SQLITE_TEMP_DB_FILE_PATH } = grabDirNames();
const { db_file_path } = grabDBDir({ config });
const isVector = Boolean(opts.vector || opts.v);
const isTypeDef = Boolean(opts.typedef || opts.t);
cpSync(db_file_path, BUN_SQLITE_TEMP_DB_FILE_PATH);
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
try {
const isVector = Boolean(opts.vector || opts.v);
const isTypeDef = Boolean(opts.typedef || opts.t);
const manager = new SQLiteSchemaManager({
schema: finaldbSchema,
recreate_vector_table: isVector,
});
await manager.syncSchema();
manager.close();
if (isTypeDef && config.typedef_file_path) {
const out_file = path.resolve(
ROOT_DIR,
config.typedef_file_path,
);
dbSchemaToTypeDef({
dbSchema: finaldbSchema,
dst_file: out_file,
config,
const finaldbSchema = appendDefaultFieldsToDbSchema({
dbSchema,
});
const manager = new SQLiteSchemaManager({
schema: finaldbSchema,
recreate_vector_table: isVector,
});
await manager.syncSchema();
manager.close();
if (isTypeDef && config.typedef_file_path) {
const out_file = path.resolve(
ROOT_DIR,
config.typedef_file_path,
);
dbSchemaToTypeDef({
dbSchema: finaldbSchema,
dst_file: out_file,
config,
});
}
writeLiveSchema({ schema: finaldbSchema });
console.log(
`${chalk.bold(chalk.green(`DB Schema setup success!`))}`,
);
process.exit();
} catch (error) {
console.log(error);
cpSync(BUN_SQLITE_TEMP_DB_FILE_PATH, db_file_path);
process.exit(1);
}
writeLiveSchema({ schema: finaldbSchema });
console.log(
`${chalk.bold(chalk.green(`DB Schema setup success!`))}`,
);
process.exit();
});
}

View File

@ -9,6 +9,10 @@ export default function grabDirNames(params?: Params) {
const ROOT_DIR = process.cwd();
const BUN_SQLITE_DIR = path.join(ROOT_DIR, ".bun-sqlite");
const BUN_SQLITE_TEMP_DIR = path.join(BUN_SQLITE_DIR, ".tmp");
const BUN_SQLITE_TEMP_DB_FILE_PATH = path.join(
BUN_SQLITE_TEMP_DIR,
"temp.db",
);
const BUN_SQLITE_LIVE_SCHEMA = path.join(
BUN_SQLITE_DIR,
"live-schema.json",
@ -19,5 +23,6 @@ export default function grabDirNames(params?: Params) {
BUN_SQLITE_DIR,
BUN_SQLITE_TEMP_DIR,
BUN_SQLITE_LIVE_SCHEMA,
BUN_SQLITE_TEMP_DB_FILE_PATH,
};
}

View File

@ -0,0 +1,35 @@
import init from "../functions/init";
type Params = {
sql: string;
table: string;
data: any[];
};
export default async function ({ sql: passed_sql, table, data }: Params) {
let sql = passed_sql;
const { dbSchema } = await init();
const table_schema = dbSchema.tables.find((t) => t.tableName == table);
if (table_schema?.tableName) {
const set_sql = Object.keys(data[0])
.map((field) => `${field} = excluded.${field}`)
.join(", ");
const unique_fields = table_schema.fields.filter((f) => f.unique);
for (let i = 0; i < unique_fields.length; i++) {
const field = unique_fields[i];
sql += ` ON CONFLICT(${field?.fieldName}) DO UPDATE SET ${set_sql}`;
}
if (table_schema.uniqueConstraints?.[0]) {
for (let i = 0; i < table_schema.uniqueConstraints.length; i++) {
const constraint = table_schema.uniqueConstraints[i];
sql += ` ON CONFLICT(${constraint?.constraintTableFields?.map((c) => c.value)?.join(", ")}) DO UPDATE SET ${set_sql}`;
}
}
}
return sql;
}

View File

@ -1,6 +1,7 @@
import DbClient from ".";
import type { APIResponseObject, SQLInsertGenReturn } from "../../types";
import sqlInsertGenerator from "../../utils/sql-insert-generator";
import grabDuplicateSafeInsertSql from "../grab-duplicate-safe-insert-sql";
type Params<
Schema extends { [k: string]: any } = { [k: string]: any },
@ -8,12 +9,17 @@ type Params<
> = {
table: Table;
data: Schema[];
update_on_duplicate?: boolean;
};
export default async function DbInsert<
Schema extends { [k: string]: any } = { [k: string]: any },
Table extends string = string,
>({ table, data }: Params<Schema, Table>): Promise<APIResponseObject> {
>({
table,
data,
update_on_duplicate,
}: Params<Schema, Table>): Promise<APIResponseObject> {
let sqlObj: SQLInsertGenReturn | null = null;
try {
@ -29,7 +35,15 @@ export default async function DbInsert<
data: finalData as any[],
}) || null;
const res = DbClient.run(sqlObj?.query || "", sqlObj?.values || []);
let sql = sqlObj?.query || "";
if (update_on_duplicate && data[0]) {
sql = await grabDuplicateSafeInsertSql({ data, table, sql });
}
(sqlObj || ({} as any)).query = sql;
const res = DbClient.run(sql, sqlObj?.values || []);
return {
success: Boolean(Number(res.lastInsertRowid)),

View File

@ -434,11 +434,7 @@ class SQLiteSchemaManager {
// Data type mapping
const dataType = this.mapDataType(field);
if (dataType == "BLOB") {
parts.push("FLOAT[128]");
} else {
parts.push(dataType);
}
parts.push(dataType);
// Primary key
if (field.primaryKey) {