From e6af7de8654074ac5643dc6c40a009f4bfe981df Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Fri, 10 Apr 2026 21:13:11 +0100 Subject: [PATCH] Updates --- dist/commands/schema.js | 51 ++++++++------ dist/data/grab-dir-names.d.ts | 1 + dist/data/grab-dir-names.js | 2 + dist/lib/grab-duplicate-safe-insert-sql.d.ts | 7 ++ dist/lib/grab-duplicate-safe-insert-sql.js | 23 +++++++ dist/lib/sqlite/db-insert.d.ts | 3 +- dist/lib/sqlite/db-insert.js | 10 ++- dist/lib/sqlite/db-schema-manager.js | 7 +- package.json | 2 +- src/commands/schema.ts | 71 ++++++++++++-------- src/data/grab-dir-names.ts | 5 ++ src/lib/grab-duplicate-safe-insert-sql.ts | 35 ++++++++++ src/lib/sqlite/db-insert.ts | 18 ++++- src/lib/sqlite/db-schema-manager.ts | 6 +- 14 files changed, 176 insertions(+), 65 deletions(-) create mode 100644 dist/lib/grab-duplicate-safe-insert-sql.d.ts create mode 100644 dist/lib/grab-duplicate-safe-insert-sql.js create mode 100644 src/lib/grab-duplicate-safe-insert-sql.ts diff --git a/dist/commands/schema.js b/dist/commands/schema.js index aaa3865..04a9236 100644 --- a/dist/commands/schema.js +++ b/dist/commands/schema.js @@ -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(); }); } diff --git a/dist/data/grab-dir-names.d.ts b/dist/data/grab-dir-names.d.ts index d2f6f4c..9a3bf1a 100644 --- a/dist/data/grab-dir-names.d.ts +++ b/dist/data/grab-dir-names.d.ts @@ -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 {}; diff --git a/dist/data/grab-dir-names.js b/dist/data/grab-dir-names.js index a33389f..c78177f 100644 --- a/dist/data/grab-dir-names.js +++ b/dist/data/grab-dir-names.js @@ -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, }; } diff --git a/dist/lib/grab-duplicate-safe-insert-sql.d.ts b/dist/lib/grab-duplicate-safe-insert-sql.d.ts new file mode 100644 index 0000000..00db688 --- /dev/null +++ b/dist/lib/grab-duplicate-safe-insert-sql.d.ts @@ -0,0 +1,7 @@ +type Params = { + sql: string; + table: string; + data: any[]; +}; +export default function ({ sql: passed_sql, table, data }: Params): Promise; +export {}; diff --git a/dist/lib/grab-duplicate-safe-insert-sql.js b/dist/lib/grab-duplicate-safe-insert-sql.js new file mode 100644 index 0000000..16b7f77 --- /dev/null +++ b/dist/lib/grab-duplicate-safe-insert-sql.js @@ -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; +} diff --git a/dist/lib/sqlite/db-insert.d.ts b/dist/lib/sqlite/db-insert.d.ts index 62725f7..d5101ec 100644 --- a/dist/lib/sqlite/db-insert.d.ts +++ b/dist/lib/sqlite/db-insert.d.ts @@ -6,10 +6,11 @@ type Params = { table: Table; data: Schema[]; + update_on_duplicate?: boolean; }; export default function DbInsert({ table, data }: Params): Promise; +}, Table extends string = string>({ table, data, update_on_duplicate, }: Params): Promise; export {}; diff --git a/dist/lib/sqlite/db-insert.js b/dist/lib/sqlite/db-insert.js index c30dd9e..c007017 100644 --- a/dist/lib/sqlite/db-insert.js +++ b/dist/lib/sqlite/db-insert.js @@ -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: { diff --git a/dist/lib/sqlite/db-schema-manager.js b/dist/lib/sqlite/db-schema-manager.js index 7add6e5..9107040 100644 --- a/dist/lib/sqlite/db-schema-manager.js +++ b/dist/lib/sqlite/db-schema-manager.js @@ -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"); diff --git a/package.json b/package.json index 895d077..287028f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/commands/schema.ts b/src/commands/schema.ts index 5de2252..49a0f2b 100644 --- a/src/commands/schema.ts +++ b/src/commands/schema.ts @@ -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(); }); } diff --git a/src/data/grab-dir-names.ts b/src/data/grab-dir-names.ts index b04b7c7..3408f53 100644 --- a/src/data/grab-dir-names.ts +++ b/src/data/grab-dir-names.ts @@ -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, }; } diff --git a/src/lib/grab-duplicate-safe-insert-sql.ts b/src/lib/grab-duplicate-safe-insert-sql.ts new file mode 100644 index 0000000..2383485 --- /dev/null +++ b/src/lib/grab-duplicate-safe-insert-sql.ts @@ -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; +} diff --git a/src/lib/sqlite/db-insert.ts b/src/lib/sqlite/db-insert.ts index 3e60b3e..a544289 100644 --- a/src/lib/sqlite/db-insert.ts +++ b/src/lib/sqlite/db-insert.ts @@ -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): Promise { +>({ + table, + data, + update_on_duplicate, +}: Params): Promise { 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)), diff --git a/src/lib/sqlite/db-schema-manager.ts b/src/lib/sqlite/db-schema-manager.ts index bf53eb3..27cf859 100644 --- a/src/lib/sqlite/db-schema-manager.ts +++ b/src/lib/sqlite/db-schema-manager.ts @@ -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) {