This commit is contained in:
Benjamin Toby 2026-03-02 10:44:30 +01:00
parent e293205e66
commit 634be9b01d
52 changed files with 3033 additions and 3 deletions

1
.gitignore vendored
View File

@ -3,7 +3,6 @@ node_modules
# output # output
out out
dist
*.tgz *.tgz
# code coverage # code coverage

1
.npmrc
View File

@ -1 +1,2 @@
@moduletrace:registry=https://git.tben.me/api/packages/moduletrace/npm/ @moduletrace:registry=https://git.tben.me/api/packages/moduletrace/npm/
//git.tben.me/api/packages/moduletrace/npm/:_authToken=${GITBEN_NPM_TOKEN}

6
dist/commands/index.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bun
/**
* # Declare Global Variables
*/
declare global { }
export {};

27
dist/commands/index.js vendored Normal file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env bun
import { program } from "commander";
import schema from "./schema";
import typedef from "./typedef";
/**
* # Describe Program
*/
program
.name(`bun-sqlite`)
.description(`SQLite manager for Bun`)
.version(`1.0.0`);
/**
* # Declare Commands
*/
program.addCommand(schema());
program.addCommand(typedef());
/**
* # Handle Unavailable Commands
*/
program.on("command:*", () => {
console.error("Invalid command: %s\nSee --help for a list of available commands.", program.args.join(" "));
process.exit(1);
});
/**
* # Parse Arguments
*/
program.parse(Bun.argv);

2
dist/commands/schema.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import { Command } from "commander";
export default function (): Command;

37
dist/commands/schema.js vendored Normal file
View File

@ -0,0 +1,37 @@
import { Command } from "commander";
import { SQLiteSchemaManager } from "../lib/sqlite/db-schema-manager";
import init from "../functions/init";
import grabDirNames from "../data/grab-dir-names";
import path from "path";
import dbSchemaToTypeDef from "../lib/sqlite/schema-to-typedef";
import _ from "lodash";
import { DefaultFields } from "../types";
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
export default function () {
return new Command("schema")
.description("Build DB From Schema")
.option("-v, --vector", "Recreate Vector Tables. This will drop and rebuild all vector tables")
.option("-t, --typedef", "Generate typescript type definitions")
.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,
});
}
process.exit();
});
}

2
dist/commands/typedef.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import { Command } from "commander";
export default function (): Command;

28
dist/commands/typedef.js vendored Normal file
View File

@ -0,0 +1,28 @@
import { Command } from "commander";
import init from "../functions/init";
import dbSchemaToTypeDef from "../lib/sqlite/schema-to-typedef";
import path from "path";
import grabDirNames from "../data/grab-dir-names";
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
export default function () {
return new Command("typedef")
.description("Build DB From Schema")
.action(async (opts) => {
console.log(`Creating Type Definition From DB Schema ...`);
const { config, dbSchema } = await init();
const { ROOT_DIR } = grabDirNames();
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
if (config.typedef_file_path) {
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
dbSchemaToTypeDef({
dbSchema: finaldbSchema,
dst_file: out_file,
});
}
else {
console.error(``);
process.exit(1);
}
process.exit();
});
}

3
dist/data/app-data.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
export declare const AppData: {
readonly ConfigFileName: "bun-sqlite.config.ts";
};

3
dist/data/app-data.js vendored Normal file
View File

@ -0,0 +1,3 @@
export const AppData = {
ConfigFileName: "bun-sqlite.config.ts",
};

3
dist/data/grab-dir-names.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
export default function grabDirNames(): {
ROOT_DIR: string;
};

7
dist/data/grab-dir-names.js vendored Normal file
View File

@ -0,0 +1,7 @@
import path from "path";
export default function grabDirNames() {
const ROOT_DIR = process.cwd();
return {
ROOT_DIR,
};
}

2
dist/functions/init.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import type { BunSQLiteConfigReturn } from "../types";
export default function init(): Promise<BunSQLiteConfigReturn>;

49
dist/functions/init.js vendored Normal file
View File

@ -0,0 +1,49 @@
import path from "path";
import fs from "fs";
import { AppData } from "../data/app-data";
import grabDirNames from "../data/grab-dir-names";
export default async function init() {
try {
const { ROOT_DIR } = grabDirNames();
const { ConfigFileName } = AppData;
const ConfigFilePath = path.join(ROOT_DIR, ConfigFileName);
if (!fs.existsSync(ConfigFilePath)) {
console.log("ConfigFilePath", ConfigFilePath);
console.error(`Please create a \`${ConfigFileName}\` file at the root of your project.`);
process.exit(1);
}
const ConfigImport = await import(ConfigFilePath);
const Config = ConfigImport["default"];
if (!Config.db_name) {
console.error(`\`db_name\` is required in your config`);
process.exit(1);
}
if (!Config.db_schema_file_name) {
console.error(`\`db_schema_file_name\` is required in your config`);
process.exit(1);
}
if (!Config.db_backup_dir) {
console.error(`\`db_backup_dir\` is required in your config`);
process.exit(1);
}
let db_dir = ROOT_DIR;
if (Config.db_dir) {
db_dir = path.resolve(ROOT_DIR, Config.db_dir);
if (!fs.existsSync(Config.db_dir)) {
fs.mkdirSync(Config.db_dir, { recursive: true });
}
}
const DBSchemaFilePath = path.join(db_dir, Config.db_schema_file_name);
const DbSchemaImport = await import(DBSchemaFilePath);
const DbSchema = DbSchemaImport["default"];
const BackupDir = path.resolve(db_dir, Config.db_backup_dir);
if (!fs.existsSync(BackupDir)) {
fs.mkdirSync(BackupDir, { recursive: true });
}
return { config: Config, dbSchema: DbSchema };
}
catch (error) {
console.error(`Initialization ERROR => ` + error.message);
process.exit(1);
}
}

13
dist/index.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import DbDelete from "./lib/sqlite/db-delete";
import DbInsert from "./lib/sqlite/db-insert";
import DbSelect from "./lib/sqlite/db-select";
import DbSQL from "./lib/sqlite/db-sql";
import DbUpdate from "./lib/sqlite/db-update";
declare const BunSQLite: {
readonly select: typeof DbSelect;
readonly insert: typeof DbInsert;
readonly update: typeof DbUpdate;
readonly delete: typeof DbDelete;
readonly sql: typeof DbSQL;
};
export default BunSQLite;

13
dist/index.js vendored Normal file
View File

@ -0,0 +1,13 @@
import DbDelete from "./lib/sqlite/db-delete";
import DbInsert from "./lib/sqlite/db-insert";
import DbSelect from "./lib/sqlite/db-select";
import DbSQL from "./lib/sqlite/db-sql";
import DbUpdate from "./lib/sqlite/db-update";
const BunSQLite = {
select: DbSelect,
insert: DbInsert,
update: DbUpdate,
delete: DbDelete,
sql: DbSQL,
};
export default BunSQLite;

16
dist/lib/sqlite/db-delete.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
import type { APIResponseObject, ServerQueryParam } from "../../types";
type Params<T extends {
[k: string]: any;
} = {
[k: string]: any;
}> = {
table: string;
query?: ServerQueryParam<T>;
targetId?: number | string;
};
export default function DbDelete<T extends {
[k: string]: any;
} = {
[k: string]: any;
}>({ table, query, targetId }: Params<T>): Promise<APIResponseObject>;
export {};

49
dist/lib/sqlite/db-delete.js vendored Normal file
View File

@ -0,0 +1,49 @@
import DbClient from ".";
import _ from "lodash";
import sqlGenerator from "../../utils/sql-generator";
export default async function DbDelete({ table, query, targetId }) {
try {
let finalQuery = query || {};
if (targetId) {
finalQuery = _.merge(finalQuery, {
query: {
id: {
value: String(targetId),
},
},
});
}
const sqlQueryObj = sqlGenerator({
tableName: table,
genObject: finalQuery,
});
const whereClause = sqlQueryObj.string.match(/WHERE .*/)?.[0];
if (whereClause) {
let sql = `DELETE FROM ${table} ${whereClause}`;
const res = DbClient.run(sql, sqlQueryObj.values);
return {
success: Boolean(res.changes),
postInsertReturn: {
affectedRows: res.changes,
insertId: Number(res.lastInsertRowid),
},
debug: {
sql,
values: sqlQueryObj.values,
},
};
}
else {
return {
success: false,
msg: `No WHERE clause`,
};
}
}
catch (error) {
return {
success: false,
error: error.message,
};
}
}

View File

@ -0,0 +1,15 @@
import type { BUN_SQLITE_TableSchemaType } from "../../types";
type Param = {
paradigm: "JavaScript" | "TypeScript" | undefined;
table: BUN_SQLITE_TableSchemaType;
query?: any;
typeDefName?: string;
allValuesOptional?: boolean;
addExport?: boolean;
dbName?: string;
};
export default function generateTypeDefinition({ paradigm, table, query, typeDefName, allValuesOptional, addExport, dbName, }: Param): {
typeDefinition: string | null;
tdName: string;
};
export {};

View File

@ -0,0 +1,61 @@
export default function generateTypeDefinition({ paradigm, table, query, typeDefName, allValuesOptional, addExport, dbName, }) {
let typeDefinition = ``;
let tdName = ``;
try {
tdName = typeDefName
? typeDefName
: dbName
? `BUN_SQLITE_${dbName}_${table.tableName}`.toUpperCase()
: `BUN_SQLITE_${query.single}_${query.single_table}`.toUpperCase();
const fields = table.fields;
function typeMap(schemaType) {
if (schemaType.options && schemaType.options.length > 0) {
return schemaType.options
.map((opt) => schemaType.dataType?.match(/int/i) ||
typeof opt == "number"
? `${opt}`
: `"${opt}"`)
.join(" | ");
}
if (schemaType.dataType?.match(/int|double|decimal/i)) {
return "number";
}
if (schemaType.dataType?.match(/text|varchar|timestamp/i)) {
return "string";
}
if (schemaType.dataType?.match(/boolean/i)) {
return "0 | 1";
}
return "string";
}
const typesArrayTypeScript = [];
const typesArrayJavascript = [];
typesArrayTypeScript.push(`${addExport ? "export " : ""}type ${tdName} = {`);
typesArrayJavascript.push(`/**\n * @typedef {object} ${tdName}`);
fields.forEach((field) => {
if (field.fieldDescription) {
typesArrayTypeScript.push(` /** \n * ${field.fieldDescription}\n */`);
}
const nullValue = allValuesOptional
? "?"
: field.notNullValue
? ""
: "?";
typesArrayTypeScript.push(` ${field.fieldName}${nullValue}: ${typeMap(field)};`);
typesArrayJavascript.push(` * @property {${typeMap(field)}${nullValue}} ${field.fieldName}`);
});
typesArrayTypeScript.push(`}`);
typesArrayJavascript.push(` */`);
if (paradigm?.match(/javascript/i)) {
typeDefinition = typesArrayJavascript.join("\n");
}
if (paradigm?.match(/typescript/i)) {
typeDefinition = typesArrayTypeScript.join("\n");
}
}
catch (error) {
console.log(error.message);
typeDefinition = null;
}
return { typeDefinition, tdName };
}

15
dist/lib/sqlite/db-insert.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
import type { APIResponseObject } from "../../types";
type Params<T extends {
[k: string]: any;
} = {
[k: string]: any;
}> = {
table: string;
data: T[];
};
export default function DbInsert<T extends {
[k: string]: any;
} = {
[k: string]: any;
}>({ table, data }: Params<T>): Promise<APIResponseObject>;
export {};

32
dist/lib/sqlite/db-insert.js vendored Normal file
View File

@ -0,0 +1,32 @@
import DbClient from ".";
import sqlInsertGenerator from "../../utils/sql-insert-generator";
export default async function DbInsert({ table, data }) {
try {
const finalData = data.map((d) => ({
...d,
created_at: Date.now(),
updated_at: Date.now(),
}));
const sqlObj = sqlInsertGenerator({
tableName: table,
data: finalData,
});
const res = DbClient.run(sqlObj?.query || "", sqlObj?.values || []);
return {
success: Boolean(Number(res.lastInsertRowid)),
postInsertReturn: {
affectedRows: res.changes,
insertId: Number(res.lastInsertRowid),
},
debug: {
sqlObj,
},
};
}
catch (error) {
return {
success: false,
error: error.message,
};
}
}

72
dist/lib/sqlite/db-schema-manager.d.ts vendored Normal file
View File

@ -0,0 +1,72 @@
#!/usr/bin/env bun
import type { BUN_SQLITE_DatabaseSchemaType } from "../../types";
declare class SQLiteSchemaManager {
private db;
private db_manager_table_name;
private recreate_vector_table;
private db_schema;
constructor({ schema, recreate_vector_table, }: {
schema: BUN_SQLITE_DatabaseSchemaType;
recreate_vector_table?: boolean;
});
private createDbManagerTable;
private insertDbManagerTable;
private removeDbManagerTable;
/**
* Main synchronization method
*/
syncSchema(): Promise<void>;
/**
* Get list of existing tables in the database
*/
private getExistingTables;
/**
* Drop tables that are no longer in the schema
*/
private dropRemovedTables;
/**
* Sync a single table (create or update)
*/
private syncTable;
/**
* Create a new table
*/
private createTable;
/**
* Update an existing table
*/
private updateTable;
/**
* Get existing columns for a table
*/
private getTableColumns;
/**
* Add a new column to existing table
*/
private addColumn;
/**
* Recreate table (for complex schema changes)
*/
private recreateTable;
/**
* Build column definition SQL
*/
private buildColumnDefinition;
/**
* Map DSQL data types to SQLite types
*/
private mapDataType;
/**
* Build foreign key constraint
*/
private buildForeignKeyConstraint;
/**
* Sync indexes for a table
*/
private syncIndexes;
/**
* Close database connection
*/
close(): void;
}
export { SQLiteSchemaManager };

456
dist/lib/sqlite/db-schema-manager.js vendored Normal file
View File

@ -0,0 +1,456 @@
#!/usr/bin/env bun
import { Database } from "bun:sqlite";
import _ from "lodash";
import DbClient from ".";
// Schema Manager Class
class SQLiteSchemaManager {
db;
db_manager_table_name;
recreate_vector_table;
db_schema;
constructor({ schema, recreate_vector_table = false, }) {
this.db = DbClient;
this.db_manager_table_name = "__db_schema_manager__";
this.db.run("PRAGMA foreign_keys = ON;");
this.recreate_vector_table = recreate_vector_table;
this.createDbManagerTable();
this.db_schema = schema;
}
createDbManagerTable() {
this.db.run(`
CREATE TABLE IF NOT EXISTS ${this.db_manager_table_name} (
table_name TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
}
insertDbManagerTable(tableName) {
this.db.run(`INSERT INTO ${this.db_manager_table_name} (table_name,created_at,updated_at) VALUES (?, ?, ?)`, [tableName, Date.now(), Date.now()]);
}
removeDbManagerTable(tableName) {
this.db.run(`DELETE FROM ${this.db_manager_table_name} WHERE table_name = ?`, [tableName]);
}
/**
* Main synchronization method
*/
async syncSchema() {
console.log("Starting schema synchronization...");
const existingTables = this.getExistingTables();
const schemaTables = this.db_schema.tables.map((t) => t.tableName);
// 2. Create or update tables
for (const table of this.db_schema.tables) {
await this.syncTable(table, existingTables);
}
// 1. Drop tables that no longer exist in schema
await this.dropRemovedTables(existingTables, schemaTables);
console.log("Schema synchronization complete!");
}
/**
* Get list of existing tables in the database
*/
getExistingTables() {
let sql = `SELECT table_name FROM ${this.db_manager_table_name}`;
const query = this.db.query(sql);
const results = query.all();
return results.map((r) => r.table_name);
}
/**
* Drop tables that are no longer in the schema
*/
async dropRemovedTables(existingTables, schemaTables) {
const tablesToDrop = existingTables.filter((t) => !schemaTables.includes(t) &&
!schemaTables.find((scT) => t.startsWith(scT + "_")));
for (const tableName of tablesToDrop) {
console.log(`Dropping table: ${tableName}`);
this.db.run(`DROP TABLE IF EXISTS "${tableName}"`);
this.db.run(`DELETE FROM ${this.db_manager_table_name} WHERE table_name = "${tableName}"`);
}
}
/**
* Sync a single table (create or update)
*/
async syncTable(table, existingTables) {
let tableExists = existingTables.includes(table.tableName);
// Handle table rename
if (table.tableNameOld && table.tableNameOld !== table.tableName) {
if (existingTables.includes(table.tableNameOld)) {
console.log(`Renaming table: ${table.tableNameOld} -> ${table.tableName}`);
this.db.run(`ALTER TABLE "${table.tableNameOld}" RENAME TO "${table.tableName}"`);
this.insertDbManagerTable(table.tableName);
this.removeDbManagerTable(table.tableNameOld);
tableExists = true;
}
}
if (!tableExists) {
// Create new table
await this.createTable(table);
this.insertDbManagerTable(table.tableName);
}
else {
// Update existing table
await this.updateTable(table);
}
// Sync indexes
await this.syncIndexes(table);
}
/**
* Create a new table
*/
async createTable(table) {
console.log(`Creating table: ${table.tableName}`);
let new_table = _.cloneDeep(table);
if (new_table.parentTableName) {
const parent_table = this.db_schema.tables.find((t) => t.tableName === new_table.parentTableName);
if (!parent_table) {
throw new Error(`Parent table \`${new_table.parentTableName}\` not found for \`${new_table.tableName}\``);
}
new_table = _.merge(parent_table, {
tableName: new_table.tableName,
tableDescription: new_table.tableDescription,
});
}
const columns = [];
const foreignKeys = [];
for (const field of new_table.fields) {
const columnDef = this.buildColumnDefinition(field);
columns.push(columnDef);
if (field.foreignKey) {
foreignKeys.push(this.buildForeignKeyConstraint(field));
}
}
// Add unique constraints
if (new_table.uniqueConstraints) {
for (const constraint of new_table.uniqueConstraints) {
if (constraint.constraintTableFields &&
constraint.constraintTableFields.length > 0) {
const fields = constraint.constraintTableFields
.map((f) => `"${f.value}"`)
.join(", ");
const constraintName = constraint.constraintName ||
`unique_${fields.replace(/"/g, "")}`;
columns.push(`CONSTRAINT "${constraintName}" UNIQUE (${fields})`);
}
}
}
const allConstraints = [...columns, ...foreignKeys];
const sql = new_table.isVector
? `CREATE VIRTUAL TABLE "${new_table.tableName}" USING ${new_table.vectorType || "vec0"}(${allConstraints.join(", ")})`
: `CREATE TABLE "${new_table.tableName}" (${allConstraints.join(", ")})`;
this.db.run(sql);
}
/**
* Update an existing table
*/
async updateTable(table) {
console.log(`Updating table: ${table.tableName}`);
const existingColumns = this.getTableColumns(table.tableName);
const schemaColumns = table.fields.map((f) => f.fieldName || "");
// SQLite has limited ALTER TABLE support
// We need to use the recreation strategy for complex changes
const columnsToAdd = table.fields.filter((f) => f.fieldName &&
!existingColumns.find((c) => c.name == f.fieldName && c.type == this.mapDataType(f)));
const columnsToRemove = existingColumns.filter((c) => !schemaColumns.includes(c.name));
const columnsToUpdate = table.fields.filter((f) => f.fieldName &&
f.updatedField &&
existingColumns.find((c) => c.name == f.fieldName && c.type == this.mapDataType(f)));
// Simple case: only adding columns
if (columnsToRemove.length === 0 && columnsToUpdate.length === 0) {
for (const field of columnsToAdd) {
await this.addColumn(table.tableName, field);
}
}
else {
// Complex case: need to recreate table
await this.recreateTable(table);
}
}
/**
* Get existing columns for a table
*/
getTableColumns(tableName) {
const query = this.db.query(`PRAGMA table_info("${tableName}")`);
const results = query.all();
return results;
}
/**
* Add a new column to existing table
*/
async addColumn(tableName, field) {
console.log(`Adding column: ${tableName}.${field.fieldName}`);
const columnDef = this.buildColumnDefinition(field);
// Remove PRIMARY KEY and UNIQUE constraints for ALTER TABLE ADD COLUMN
const cleanDef = columnDef
.replace(/PRIMARY KEY/gi, "")
.replace(/AUTOINCREMENT/gi, "")
.replace(/UNIQUE/gi, "")
.trim();
const sql = `ALTER TABLE "${tableName}" ADD COLUMN ${cleanDef}`;
this.db.run(sql);
}
/**
* Recreate table (for complex schema changes)
*/
async recreateTable(table) {
if (table.isVector) {
if (!this.recreate_vector_table) {
return;
}
console.log(`Recreating vector table: ${table.tableName}`);
const existingRows = this.db
.query(`SELECT * FROM "${table.tableName}"`)
.all();
this.db.run(`DROP TABLE "${table.tableName}"`);
await this.createTable(table);
if (existingRows.length > 0) {
for (let i = 0; i < existingRows.length; i++) {
const row = existingRows[i];
if (!row)
continue;
const columns = Object.keys(row);
const placeholders = columns.map(() => "?").join(", ");
this.db.run(`INSERT INTO "${table.tableName}" (${columns.join(", ")}) VALUES (${placeholders})`, Object.values(row));
}
}
return;
}
const tempTableName = `${table.tableName}_temp_${Date.now()}`;
// Get existing data
const existingColumns = this.getTableColumns(table.tableName);
const columnsToKeep = table.fields
.filter((f) => f.fieldName &&
existingColumns.find((c) => c.name == f.fieldName &&
c.type == this.mapDataType(f)))
.map((f) => f.fieldName);
// Create temp table with new schema
const tempTable = { ...table, tableName: tempTableName };
await this.createTable(tempTable);
// Copy data if there are common columns
if (columnsToKeep.length > 0) {
const columnList = columnsToKeep.map((c) => `"${c}"`).join(", ");
this.db.run(`INSERT INTO "${tempTableName}" (${columnList}) SELECT ${columnList} FROM "${table.tableName}"`);
}
// Drop old table
this.db.run(`DROP TABLE "${table.tableName}"`);
// Rename temp table
this.db.run(`ALTER TABLE "${tempTableName}" RENAME TO "${table.tableName}"`);
}
/**
* Build column definition SQL
*/
buildColumnDefinition(field) {
if (!field.fieldName) {
throw new Error("Field name is required");
}
const fieldName = field.sideCar
? `+${field.fieldName}`
: `${field.fieldName}`;
const parts = [fieldName];
// Data type mapping
const dataType = this.mapDataType(field);
parts.push(dataType);
// Primary key
if (field.primaryKey) {
parts.push("PRIMARY KEY");
if (field.autoIncrement) {
parts.push("AUTOINCREMENT");
}
}
// Not null
if (field.notNullValue || field.primaryKey) {
if (!field.primaryKey) {
parts.push("NOT NULL");
}
}
// Unique
if (field.unique && !field.primaryKey) {
parts.push("UNIQUE");
}
// Default value
if (field.defaultValue !== undefined) {
if (typeof field.defaultValue === "string") {
parts.push(
// Escape single quotes by doubling them to prevent SQL injection and wrap in single quotes
`DEFAULT '${field.defaultValue.replace(/'/g, "''")}'`);
}
else {
parts.push(`DEFAULT ${field.defaultValue}`);
}
}
else if (field.defaultValueLiteral) {
parts.push(`DEFAULT ${field.defaultValueLiteral}`);
}
return parts.join(" ");
}
/**
* Map DSQL data types to SQLite types
*/
mapDataType(field) {
const dataType = field.dataType?.toLowerCase() || "text";
const vectorSize = field.vectorSize || 1536;
// Vector Embeddings
if (field.isVector) {
return `FLOAT[${vectorSize}]`;
}
// Integer types
if (dataType.includes("int") ||
dataType === "bigint" ||
dataType === "smallint" ||
dataType === "tinyint") {
return "INTEGER";
}
// Real/Float types
if (dataType.includes("real") ||
dataType.includes("float") ||
dataType.includes("double") ||
dataType === "decimal" ||
dataType === "numeric") {
return "REAL";
}
// Blob types
if (dataType.includes("blob") || dataType.includes("binary")) {
return "BLOB";
}
// Boolean
if (dataType === "boolean" || dataType === "bool") {
return "INTEGER"; // SQLite uses INTEGER for boolean (0/1)
}
// Date/Time types
if (dataType.includes("date") || dataType.includes("time")) {
return "TEXT"; // SQLite stores dates as TEXT or INTEGER
}
// Default to TEXT for all text-based types
return "TEXT";
}
/**
* Build foreign key constraint
*/
buildForeignKeyConstraint(field) {
const fk = field.foreignKey;
let constraint = `FOREIGN KEY ("${field.fieldName}") REFERENCES "${fk.destinationTableName}"("${fk.destinationTableColumnName}")`;
if (fk.cascadeDelete) {
constraint += " ON DELETE CASCADE";
}
if (fk.cascadeUpdate) {
constraint += " ON UPDATE CASCADE";
}
return constraint;
}
/**
* Sync indexes for a table
*/
async syncIndexes(table) {
if (!table.indexes || table.indexes.length === 0) {
return;
}
// Get existing indexes
const query = this.db.query(`SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='${table.tableName}' AND name NOT LIKE 'sqlite_%'`);
const existingIndexes = query.all().map((r) => r.name);
// Drop indexes not in schema
for (const indexName of existingIndexes) {
const stillExists = table.indexes.some((idx) => idx.indexName === indexName);
if (!stillExists) {
console.log(`Dropping index: ${indexName}`);
this.db.run(`DROP INDEX IF EXISTS "${indexName}"`);
}
}
// Create new indexes
for (const index of table.indexes) {
if (!index.indexName ||
!index.indexTableFields ||
index.indexTableFields.length === 0) {
continue;
}
if (!existingIndexes.includes(index.indexName)) {
console.log(`Creating index: ${index.indexName}`);
const fields = index.indexTableFields
.map((f) => `"${f.value}"`)
.join(", ");
const unique = index.indexType === "regular" ? "" : ""; // SQLite doesn't have FULLTEXT in CREATE INDEX
this.db.run(`CREATE ${unique}INDEX "${index.indexName}" ON "${table.tableName}" (${fields})`);
}
}
}
/**
* Close database connection
*/
close() {
this.db.close();
}
}
// Example usage
async function main() {
const schema = {
dbName: "example_db",
tables: [
{
tableName: "users",
tableDescription: "User accounts",
fields: [
{
fieldName: "id",
dataType: "INTEGER",
primaryKey: true,
autoIncrement: true,
},
{
fieldName: "username",
dataType: "TEXT",
notNullValue: true,
unique: true,
},
{
fieldName: "email",
dataType: "TEXT",
notNullValue: true,
},
{
fieldName: "created_at",
dataType: "TEXT",
defaultValueLiteral: "CURRENT_TIMESTAMP",
},
],
indexes: [
{
indexName: "idx_users_email",
indexType: "regular",
indexTableFields: [
{ value: "email", dataType: "TEXT" },
],
},
],
},
{
tableName: "posts",
fields: [
{
fieldName: "id",
dataType: "INTEGER",
primaryKey: true,
autoIncrement: true,
},
{
fieldName: "user_id",
dataType: "INTEGER",
notNullValue: true,
foreignKey: {
destinationTableName: "users",
destinationTableColumnName: "id",
cascadeDelete: true,
},
},
{
fieldName: "title",
dataType: "TEXT",
notNullValue: true,
},
{
fieldName: "content",
dataType: "TEXT",
},
],
},
],
};
}
export { SQLiteSchemaManager };

View File

@ -0,0 +1,6 @@
import type { BUN_SQLITE_DatabaseSchemaType } from "../../types";
type Params = {
dbSchema?: BUN_SQLITE_DatabaseSchemaType;
};
export default function dbSchemaToType(params?: Params): string[] | undefined;
export {};

44
dist/lib/sqlite/db-schema-to-typedef.js vendored Normal file
View File

@ -0,0 +1,44 @@
import _ from "lodash";
import generateTypeDefinition from "./db-generate-type-defs";
export default function dbSchemaToType(params) {
let datasquirelSchema = params?.dbSchema;
if (!datasquirelSchema)
return;
let tableNames = `export const BunSQLiteTables = [\n${datasquirelSchema.tables
.map((tbl) => ` "${tbl.tableName}",`)
.join("\n")}\n] as const`;
const dbTablesSchemas = datasquirelSchema.tables;
const defDbName = datasquirelSchema.dbName
?.toUpperCase()
.replace(/ |\-/g, "_");
const defNames = [];
const schemas = dbTablesSchemas
.map((table) => {
let final_table = _.cloneDeep(table);
if (final_table.parentTableName) {
const parent_table = dbTablesSchemas.find((t) => t.tableName === final_table.parentTableName);
if (parent_table) {
final_table = _.merge(parent_table, {
tableName: final_table.tableName,
tableDescription: final_table.tableDescription,
});
}
}
const defObj = generateTypeDefinition({
paradigm: "TypeScript",
table: final_table,
typeDefName: `BUN_SQLITE_${defDbName}_${final_table.tableName.toUpperCase()}`,
allValuesOptional: true,
addExport: true,
});
if (defObj.tdName?.match(/./)) {
defNames.push(defObj.tdName);
}
return defObj.typeDefinition;
})
.filter((schm) => typeof schm == "string");
const allTd = defNames?.[0]
? `export type BUN_SQLITE_${defDbName}_ALL_TYPEDEFS = ${defNames.join(` & `)}`
: ``;
return [tableNames, ...schemas, allTd];
}

17
dist/lib/sqlite/db-select.d.ts vendored Normal file
View File

@ -0,0 +1,17 @@
import type { APIResponseObject, ServerQueryParam } from "../../types";
type Params<T extends {
[k: string]: any;
} = {
[k: string]: any;
}> = {
query?: ServerQueryParam<T>;
table: string;
count?: boolean;
targetId?: number | string;
};
export default function DbSelect<T extends {
[k: string]: any;
} = {
[k: string]: any;
}>({ table, query, count, targetId }: Params<T>): Promise<APIResponseObject<T>>;
export {};

48
dist/lib/sqlite/db-select.js vendored Normal file
View File

@ -0,0 +1,48 @@
import mysql from "mysql";
import DbClient from ".";
import _ from "lodash";
import sqlGenerator from "../../utils/sql-generator";
export default async function DbSelect({ table, query, count, targetId }) {
try {
let finalQuery = query || {};
if (targetId) {
finalQuery = _.merge(finalQuery, {
query: {
id: {
value: String(targetId),
},
},
});
}
const sqlObj = sqlGenerator({
tableName: table,
genObject: finalQuery,
count,
});
const sql = mysql.format(sqlObj.string, sqlObj.values);
const res = DbClient.query(sql);
const batchRes = res.all();
let resp = {
success: Boolean(batchRes[0]),
payload: batchRes,
singleRes: batchRes[0],
debug: {
sqlObj,
sql,
},
};
if (count) {
const count_val = count ? batchRes[0]?.["COUNT(*)"] : undefined;
resp["count"] = Number(count_val);
delete resp.payload;
delete resp.singleRes;
}
return resp;
}
catch (error) {
return {
success: false,
error: error.message,
};
}
}

11
dist/lib/sqlite/db-sql.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
import type { APIResponseObject } from "../../types";
type Params = {
sql: string;
values?: (string | number)[];
};
export default function DbSQL<T extends {
[k: string]: any;
} = {
[k: string]: any;
}>({ sql, values }: Params): Promise<APIResponseObject<T>>;
export {};

33
dist/lib/sqlite/db-sql.js vendored Normal file
View File

@ -0,0 +1,33 @@
import DbClient from ".";
import _ from "lodash";
export default async function DbSQL({ sql, values }) {
try {
const res = sql.match(/^select/i)
? DbClient.query(sql).all(...(values || []))
: DbClient.run(sql, values || []);
return {
success: true,
payload: Array.isArray(res) ? res : undefined,
singleRes: Array.isArray(res) ? res?.[0] : undefined,
postInsertReturn: Array.isArray(res)
? undefined
: {
affectedRows: res.changes,
insertId: Number(res.lastInsertRowid),
},
debug: {
sqlObj: {
sql,
values,
},
sql,
},
};
}
catch (error) {
return {
success: false,
error: error.message,
};
}
}

17
dist/lib/sqlite/db-update.d.ts vendored Normal file
View File

@ -0,0 +1,17 @@
import type { APIResponseObject, ServerQueryParam } from "../../types";
type Params<T extends {
[k: string]: any;
} = {
[k: string]: any;
}> = {
table: string;
data: T;
query?: ServerQueryParam<T>;
targetId?: number | string;
};
export default function DbUpdate<T extends {
[k: string]: any;
} = {
[k: string]: any;
}>({ table, data, query, targetId }: Params<T>): Promise<APIResponseObject>;
export {};

68
dist/lib/sqlite/db-update.js vendored Normal file
View File

@ -0,0 +1,68 @@
import DbClient from ".";
import _ from "lodash";
import sqlGenerator from "../../utils/sql-generator";
export default async function DbUpdate({ table, data, query, targetId }) {
try {
let finalQuery = query || {};
if (targetId) {
finalQuery = _.merge(finalQuery, {
query: {
id: {
value: String(targetId),
},
},
});
}
const sqlQueryObj = sqlGenerator({
tableName: table,
genObject: finalQuery,
});
let values = [];
const whereClause = sqlQueryObj.string.match(/WHERE .*/)?.[0];
if (whereClause) {
let sql = `UPDATE ${table} SET`;
const finalData = {
...data,
updated_at: Date.now(),
};
const keys = Object.keys(finalData);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (!key)
continue;
const isLast = i == keys.length - 1;
sql += ` ${key}=?`;
values.push(String(finalData[key]));
if (!isLast) {
sql += `,`;
}
}
sql += ` ${whereClause}`;
values = [...values, ...sqlQueryObj.values];
const res = DbClient.run(sql, values);
return {
success: Boolean(res.changes),
postInsertReturn: {
affectedRows: res.changes,
insertId: Number(res.lastInsertRowid),
},
debug: {
sql,
values,
},
};
}
else {
return {
success: false,
msg: `No WHERE clause`,
};
}
}
catch (error) {
return {
success: false,
error: error.message,
};
}
}

3
dist/lib/sqlite/index.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
import { Database } from "bun:sqlite";
declare const DbClient: Database;
export default DbClient;

17
dist/lib/sqlite/index.js vendored Normal file
View File

@ -0,0 +1,17 @@
import { Database } from "bun:sqlite";
import path from "node:path";
import * as sqliteVec from "sqlite-vec";
import grabDirNames from "../../data/grab-dir-names";
import init from "../../functions/init";
const { ROOT_DIR } = grabDirNames();
const { config } = await init();
let db_dir = ROOT_DIR;
if (config.db_dir) {
db_dir = config.db_dir;
}
const DBFilePath = path.join(db_dir, config.db_name);
const DbClient = new Database(DBFilePath, {
create: true,
});
sqliteVec.load(DbClient);
export default DbClient;

View File

@ -0,0 +1,7 @@
import type { BUN_SQLITE_DatabaseSchemaType } from "../../types";
type Params = {
dbSchema: BUN_SQLITE_DatabaseSchemaType;
dst_file: string;
};
export default function dbSchemaToTypeDef({ dbSchema, dst_file }: Params): void;
export {};

18
dist/lib/sqlite/schema-to-typedef.js vendored Normal file
View File

@ -0,0 +1,18 @@
import path from "node:path";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import dbSchemaToType from "./db-schema-to-typedef";
export default function dbSchemaToTypeDef({ dbSchema, dst_file }) {
try {
if (!dbSchema)
throw new Error("No schema found");
const definitions = dbSchemaToType({ dbSchema });
const ourfileDir = path.dirname(dst_file);
if (!existsSync(ourfileDir)) {
mkdirSync(ourfileDir, { recursive: true });
}
writeFileSync(dst_file, definitions?.join("\n\n") || "", "utf-8");
}
catch (error) {
console.log(`Schema to Typedef Error =>`, error.message);
}
}

2
dist/lib/sqlite/schema.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import type { BUN_SQLITE_DatabaseSchemaType } from "../../types";
export declare const DbSchema: BUN_SQLITE_DatabaseSchemaType;

5
dist/lib/sqlite/schema.js vendored Normal file
View File

@ -0,0 +1,5 @@
import _ from "lodash";
export const DbSchema = {
dbName: "travis-ai",
tables: [],
};

1014
dist/types/index.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

123
dist/types/index.js vendored Normal file
View File

@ -0,0 +1,123 @@
export const UsersOmitedFields = [
"password",
"social_id",
"verification_status",
"date_created",
"date_created_code",
"date_created_timestamp",
"date_updated",
"date_updated_code",
"date_updated_timestamp",
];
export const MariaDBCollations = [
"utf8mb4_bin",
"utf8mb4_unicode_520_ci",
];
export const TextFieldTypesArray = [
{ title: "Plain Text", value: "plain" },
{ title: "Rich Text", value: "richText" },
{ title: "Markdown", value: "markdown" },
{ title: "JSON", value: "json" },
{ title: "YAML", value: "yaml" },
{ title: "HTML", value: "html" },
{ title: "CSS", value: "css" },
{ title: "Javascript", value: "javascript" },
{ title: "Shell", value: "shell" },
{ title: "Code", value: "code" },
];
export const BUN_SQLITE_DATATYPES = [
{ value: "TEXT" },
{ value: "INTEGER" },
];
export const ServerQueryOperators = ["AND", "OR"];
export const ServerQueryEqualities = [
"EQUAL",
"LIKE",
"LIKE_RAW",
"LIKE_LOWER",
"LIKE_LOWER_RAW",
"NOT LIKE",
"NOT LIKE_RAW",
"NOT_LIKE_LOWER",
"NOT_LIKE_LOWER_RAW",
"NOT EQUAL",
"REGEXP",
"FULLTEXT",
"IN",
"NOT IN",
"BETWEEN",
"NOT BETWEEN",
"IS NULL",
"IS NOT NULL",
"EXISTS",
"NOT EXISTS",
"GREATER THAN",
"GREATER THAN OR EQUAL",
"LESS THAN",
"LESS THAN OR EQUAL",
"MATCH",
"MATCH_BOOLEAN",
];
export const DataCrudRequestMethods = [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS",
];
export const DataCrudRequestMethodsLowerCase = [
"get",
"post",
"put",
"patch",
"delete",
"options",
];
export const DsqlCrudActions = ["insert", "update", "delete", "get"];
export const QueryFields = [
"duplicate",
"user_id",
"delegated_user_id",
"db_id",
"table_id",
"db_slug",
];
export const DockerComposeServices = [
"setup",
"cron",
"reverse-proxy",
"webapp",
"websocket",
"static",
"db",
"maxscale",
"post-db-setup",
"web-app-post-db-setup",
"post-replica-db-setup",
"db-replica-1",
"db-replica-2",
"db-cron",
"web-app-post-db-setup",
];
export const IndexTypes = ["regular", "full_text", "vector"];
export const DefaultFields = [
{
fieldName: "id",
dataType: "INTEGER",
primaryKey: true,
autoIncrement: true,
notNullValue: true,
fieldDescription: "The unique identifier of the record.",
},
{
fieldName: "created_at",
dataType: "INTEGER",
fieldDescription: "The time when the record was created. (Unix Timestamp)",
},
{
fieldName: "updated_at",
dataType: "INTEGER",
fieldDescription: "The time when the record was updated. (Unix Timestamp)",
},
];

View File

@ -0,0 +1,6 @@
import { type BUN_SQLITE_DatabaseSchemaType } from "../types";
type Params = {
dbSchema: BUN_SQLITE_DatabaseSchemaType;
};
export default function ({ dbSchema }: Params): BUN_SQLITE_DatabaseSchemaType;
export {};

View File

@ -0,0 +1,12 @@
import _ from "lodash";
import { DefaultFields } from "../types";
export default function ({ dbSchema }) {
const finaldbSchema = _.cloneDeep(dbSchema);
finaldbSchema.tables = finaldbSchema.tables.map((t) => {
const newTable = _.cloneDeep(t);
newTable.fields = newTable.fields.filter((f) => !f.fieldName?.match(/^(id|created_at|updated_at)$/));
newTable.fields.unshift(...DefaultFields);
return newTable;
});
return finaldbSchema;
}

2
dist/utils/sql-equality-parser.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import { ServerQueryEqualities } from "../types";
export default function sqlEqualityParser(eq: (typeof ServerQueryEqualities)[number]): string;

39
dist/utils/sql-equality-parser.js vendored Normal file
View File

@ -0,0 +1,39 @@
import { ServerQueryEqualities } from "../types";
export default function sqlEqualityParser(eq) {
switch (eq) {
case "EQUAL":
return "=";
case "LIKE":
return "LIKE";
case "NOT LIKE":
return "NOT LIKE";
case "NOT EQUAL":
return "<>";
case "IN":
return "IN";
case "NOT IN":
return "NOT IN";
case "BETWEEN":
return "BETWEEN";
case "NOT BETWEEN":
return "NOT BETWEEN";
case "IS NULL":
return "IS NULL";
case "IS NOT NULL":
return "IS NOT NULL";
case "EXISTS":
return "EXISTS";
case "NOT EXISTS":
return "NOT EXISTS";
case "GREATER THAN":
return ">";
case "GREATER THAN OR EQUAL":
return ">=";
case "LESS THAN":
return "<";
case "LESS THAN OR EQUAL":
return "<=";
default:
return "=";
}
}

20
dist/utils/sql-gen-operator-gen.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
import type { ServerQueryEqualities, ServerQueryObject } from "../types";
type Params = {
fieldName: string;
value?: string;
equality?: (typeof ServerQueryEqualities)[number];
queryObj: ServerQueryObject<{
[key: string]: any;
}, string>;
isValueFieldValue?: boolean;
};
type Return = {
str?: string;
param?: string;
};
/**
* # SQL Gen Operator Gen
* @description Generates an SQL operator for node module `mysql` or `serverless-mysql`
*/
export default function sqlGenOperatorGen({ fieldName, value, equality, queryObj, isValueFieldValue, }: Params): Return;
export {};

127
dist/utils/sql-gen-operator-gen.js vendored Normal file
View File

@ -0,0 +1,127 @@
import sqlEqualityParser from "./sql-equality-parser";
/**
* # SQL Gen Operator Gen
* @description Generates an SQL operator for node module `mysql` or `serverless-mysql`
*/
export default function sqlGenOperatorGen({ fieldName, value, equality, queryObj, isValueFieldValue, }) {
if (queryObj.nullValue) {
return { str: `${fieldName} IS NULL` };
}
if (queryObj.notNullValue) {
return { str: `${fieldName} IS NOT NULL` };
}
if (value) {
const finalValue = isValueFieldValue ? value : "?";
const finalParams = isValueFieldValue ? undefined : value;
if (equality == "MATCH") {
return {
str: `MATCH(${fieldName}) AGAINST(${finalValue} IN NATURAL LANGUAGE MODE)`,
param: finalParams,
};
}
else if (equality == "MATCH_BOOLEAN") {
return {
str: `MATCH(${fieldName}) AGAINST(${finalValue} IN BOOLEAN MODE)`,
param: finalParams,
};
}
else if (equality == "LIKE_LOWER") {
return {
str: `LOWER(${fieldName}) LIKE LOWER(${finalValue})`,
param: `%${finalParams}%`,
};
}
else if (equality == "LIKE_LOWER_RAW") {
return {
str: `LOWER(${fieldName}) LIKE LOWER(${finalValue})`,
param: finalParams,
};
}
else if (equality == "LIKE") {
return {
str: `${fieldName} LIKE ${finalValue}`,
param: `%${finalParams}%`,
};
}
else if (equality == "LIKE_RAW") {
return {
str: `${fieldName} LIKE ${finalValue}`,
param: finalParams,
};
}
else if (equality == "NOT_LIKE_LOWER") {
return {
str: `LOWER(${fieldName}) NOT LIKE LOWER(${finalValue})`,
param: `%${finalParams}%`,
};
}
else if (equality == "NOT_LIKE_LOWER_RAW") {
return {
str: `LOWER(${fieldName}) NOT LIKE LOWER(${finalValue})`,
param: finalParams,
};
}
else if (equality == "NOT LIKE") {
return {
str: `${fieldName} NOT LIKE ${finalValue}`,
param: finalParams,
};
}
else if (equality == "NOT LIKE_RAW") {
return {
str: `${fieldName} NOT LIKE ${finalValue}`,
param: finalParams,
};
}
else if (equality == "REGEXP") {
return {
str: `LOWER(${fieldName}) REGEXP LOWER(${finalValue})`,
param: finalParams,
};
}
else if (equality == "FULLTEXT") {
return {
str: `MATCH(${fieldName}) AGAINST(${finalValue} IN BOOLEAN MODE)`,
param: finalParams,
};
}
else if (equality == "NOT EQUAL") {
return {
str: `${fieldName} != ${finalValue}`,
param: finalParams,
};
}
else if (equality) {
return {
str: `${fieldName} ${sqlEqualityParser(equality)} ${finalValue}`,
param: finalParams,
};
}
else {
return {
str: `${fieldName} = ${finalValue}`,
param: finalParams,
};
}
}
else {
if (equality == "IS NULL") {
return { str: `${fieldName} IS NULL` };
}
else if (equality == "IS NOT NULL") {
return { str: `${fieldName} IS NOT NULL` };
}
else if (equality) {
return {
str: `${fieldName} ${sqlEqualityParser(equality)} ?`,
param: value,
};
}
else {
return {
str: `${fieldName} = ?`,
param: value,
};
}
}
}

25
dist/utils/sql-generator.d.ts vendored Normal file
View File

@ -0,0 +1,25 @@
import type { ServerQueryParam } from "../types";
type Param<T extends {
[key: string]: any;
} = {
[key: string]: any;
}> = {
genObject?: ServerQueryParam<T>;
tableName: string;
dbFullName?: string;
count?: boolean;
};
type Return = {
string: string;
values: (string | number)[];
};
/**
* # SQL Query Generator
* @description Generates an SQL Query for node module `mysql` or `serverless-mysql`
*/
export default function sqlGenerator<T extends {
[key: string]: any;
} = {
[key: string]: any;
}>({ tableName, genObject, dbFullName, count }: Param<T>): Return;
export {};

392
dist/utils/sql-generator.js vendored Normal file
View File

@ -0,0 +1,392 @@
import { isUndefined } from "lodash";
import sqlGenOperatorGen from "./sql-gen-operator-gen";
/**
* # SQL Query Generator
* @description Generates an SQL Query for node module `mysql` or `serverless-mysql`
*/
export default function sqlGenerator({ tableName, genObject, dbFullName, count }) {
const finalQuery = genObject?.query ? genObject.query : undefined;
const queryKeys = finalQuery ? Object.keys(finalQuery) : undefined;
const sqlSearhValues = [];
const finalDbName = dbFullName ? `${dbFullName}.` : "";
/**
* # Generate Query
*/
function genSqlSrchStr({ queryObj, join, field, }) {
const finalFieldName = (() => {
if (queryObj?.tableName) {
return `${finalDbName}${queryObj.tableName}.${field}`;
}
if (join) {
return `${finalDbName}${tableName}.${field}`;
}
return field;
})();
let str = `${finalFieldName}=?`;
function grabValue(val) {
const valueParsed = val;
if (!valueParsed)
return;
const valueString = typeof valueParsed == "string"
? valueParsed
: valueParsed
? valueParsed.fieldName && valueParsed.tableName
? `${valueParsed.tableName}.${valueParsed.fieldName}`
: valueParsed.value?.toString()
: undefined;
const valueEquality = typeof valueParsed == "object"
? valueParsed.equality || queryObj.equality
: queryObj.equality;
const operatorStrParam = sqlGenOperatorGen({
queryObj,
equality: valueEquality,
fieldName: finalFieldName || "",
value: valueString?.toString() || "",
isValueFieldValue: Boolean(typeof valueParsed == "object" &&
valueParsed.fieldName &&
valueParsed.tableName),
});
return operatorStrParam;
}
if (Array.isArray(queryObj.value)) {
const strArray = [];
queryObj.value.forEach((val) => {
const operatorStrParam = grabValue(val);
if (!operatorStrParam)
return;
if (operatorStrParam.str && operatorStrParam.param) {
strArray.push(operatorStrParam.str);
sqlSearhValues.push(operatorStrParam.param);
}
else if (operatorStrParam.str) {
strArray.push(operatorStrParam.str);
}
});
str = "(" + strArray.join(` ${queryObj.operator || "AND"} `) + ")";
}
else if (typeof queryObj.value == "object") {
const operatorStrParam = grabValue(queryObj.value);
if (operatorStrParam?.str) {
str = operatorStrParam.str;
if (operatorStrParam.param) {
sqlSearhValues.push(operatorStrParam.param);
}
}
}
else {
const valueParsed = queryObj.value
? String(queryObj.value)
: undefined;
const operatorStrParam = sqlGenOperatorGen({
equality: queryObj.equality,
fieldName: finalFieldName || "",
value: valueParsed,
queryObj,
});
if (operatorStrParam.str && operatorStrParam.param) {
str = operatorStrParam.str;
sqlSearhValues.push(operatorStrParam.param);
}
else if (operatorStrParam.str) {
str = operatorStrParam.str;
}
}
return str;
}
function generateJoinStr(mtch, join) {
if (mtch.__batch) {
let btch_mtch = ``;
btch_mtch += `(`;
for (let i = 0; i < mtch.__batch.matches.length; i++) {
const __mtch = mtch.__batch.matches[i];
btch_mtch += `${generateJoinStr(__mtch, join)}`;
if (i < mtch.__batch.matches.length - 1) {
btch_mtch += ` ${mtch.__batch.operator || "OR"} `;
}
}
btch_mtch += `)`;
return btch_mtch;
}
return `${finalDbName}${typeof mtch.source == "object" ? mtch.source.tableName : tableName}.${typeof mtch.source == "object" ? mtch.source.fieldName : mtch.source}=${(() => {
if (mtch.targetLiteral) {
if (typeof mtch.targetLiteral == "number") {
return `${mtch.targetLiteral}`;
}
return `'${mtch.targetLiteral}'`;
}
if (join.alias) {
return `${finalDbName}${typeof mtch.target == "object"
? mtch.target.tableName
: join.alias}.${typeof mtch.target == "object"
? mtch.target.fieldName
: mtch.target}`;
}
return `${finalDbName}${typeof mtch.target == "object"
? mtch.target.tableName
: join.tableName}.${typeof mtch.target == "object"
? mtch.target.fieldName
: mtch.target}`;
})()}`;
}
let fullTextMatchStr = genObject?.fullTextSearch
? ` MATCH(${genObject.fullTextSearch.fields
.map((f) => genObject.join ? `${tableName}.${String(f)}` : `${String(f)}`)
.join(",")}) AGAINST (? IN BOOLEAN MODE)`
: undefined;
const fullTextSearchStr = genObject?.fullTextSearch
? genObject.fullTextSearch.searchTerm
.split(` `)
.map((t) => `${t}`)
.join(" ")
: undefined;
let queryString = (() => {
let str = "SELECT";
if (count) {
str += ` COUNT(*)`;
}
else if (genObject?.selectFields?.[0]) {
if (genObject.join) {
str += ` ${genObject.selectFields
?.map((fld) => typeof fld == "object"
? `${finalDbName}${tableName}.${fld.fieldName.toString()}` +
(fld.alias ? ` as ${fld.alias}` : ``)
: `${finalDbName}${tableName}.${String(fld)}`)
.join(",")}`;
}
else {
str += ` ${genObject.selectFields
?.map((fld) => typeof fld == "object"
? `${fld.fieldName.toString()}` +
(fld.alias ? ` as ${fld.alias}` : ``)
: fld)
.join(",")}`;
}
}
else {
if (genObject?.join) {
str += ` ${finalDbName}${tableName}.*`;
}
else {
str += " *";
}
}
if (genObject?.countSubQueries) {
let countSqls = [];
for (let i = 0; i < genObject.countSubQueries.length; i++) {
const countSubQuery = genObject.countSubQueries[i];
if (!countSubQuery)
continue;
const tableAlias = countSubQuery.table_alias;
let subQStr = `(SELECT COUNT(*)`;
subQStr += ` FROM ${countSubQuery.table}${tableAlias ? ` ${tableAlias}` : ""}`;
subQStr += ` WHERE (`;
for (let j = 0; j < countSubQuery.srcTrgMap.length; j++) {
const csqSrc = countSubQuery.srcTrgMap[j];
if (!csqSrc)
continue;
subQStr += ` ${tableAlias || countSubQuery.table}.${csqSrc.src}`;
if (typeof csqSrc.trg == "string") {
subQStr += ` = ?`;
sqlSearhValues.push(csqSrc.trg);
}
else if (typeof csqSrc.trg == "object") {
subQStr += ` = ${csqSrc.trg.table}.${csqSrc.trg.field}`;
}
if (j < countSubQuery.srcTrgMap.length - 1) {
subQStr += ` AND `;
}
}
subQStr += ` )) AS ${countSubQuery.alias}`;
countSqls.push(subQStr);
}
str += `, ${countSqls.join(",")}`;
}
if (genObject?.join && !count) {
const existingJoinTableNames = [tableName];
str +=
"," +
genObject.join
.flat()
.filter((j) => !isUndefined(j))
.map((joinObj) => {
const joinTableName = joinObj.alias
? joinObj.alias
: joinObj.tableName;
if (existingJoinTableNames.includes(joinTableName))
return null;
existingJoinTableNames.push(joinTableName);
if (joinObj.selectFields) {
return joinObj.selectFields
.map((selectField) => {
if (typeof selectField == "string") {
return `${finalDbName}${joinTableName}.${selectField}`;
}
else if (typeof selectField == "object") {
let aliasSelectField = selectField.count
? `COUNT(${finalDbName}${joinTableName}.${selectField.field})`
: `${finalDbName}${joinTableName}.${selectField.field}`;
if (selectField.alias)
aliasSelectField += ` AS ${selectField.alias}`;
return aliasSelectField;
}
})
.join(",");
}
else {
return `${finalDbName}${joinTableName}.*`;
}
})
.filter((_) => Boolean(_))
.join(",");
}
if (genObject?.fullTextSearch &&
fullTextMatchStr &&
fullTextSearchStr) {
str += `, ${fullTextMatchStr} AS ${genObject.fullTextSearch.scoreAlias}`;
sqlSearhValues.push(fullTextSearchStr);
}
str += ` FROM ${finalDbName}${tableName}`;
if (genObject?.join) {
str +=
" " +
genObject.join
.flat()
.filter((j) => !isUndefined(j))
.map((join) => {
return (join.joinType +
" " +
(join.alias
? `${finalDbName}${join.tableName}` +
" " +
join.alias
: `${finalDbName}${join.tableName}`) +
" ON " +
(() => {
if (Array.isArray(join.match)) {
return ("(" +
join.match
.map((mtch) => generateJoinStr(mtch, join))
.join(join.operator
? ` ${join.operator} `
: " AND ") +
")");
}
else if (typeof join.match == "object") {
return generateJoinStr(join.match, join);
}
})());
})
.join(" ");
}
return str;
})();
const sqlSearhString = queryKeys?.map((field) => {
const queryObj = finalQuery?.[field];
if (!queryObj)
return;
if (queryObj.__query) {
const subQueryGroup = queryObj.__query;
const subSearchKeys = Object.keys(subQueryGroup);
const subSearchString = subSearchKeys.map((_field) => {
const newSubQueryObj = subQueryGroup?.[_field];
if (newSubQueryObj) {
return genSqlSrchStr({
queryObj: newSubQueryObj,
field: newSubQueryObj.fieldName || _field,
join: genObject?.join,
});
}
});
return ("(" +
subSearchString.join(` ${queryObj.operator || "AND"} `) +
")");
}
return genSqlSrchStr({
queryObj,
field: queryObj.fieldName || field,
join: genObject?.join,
});
});
const cleanedUpSearchStr = sqlSearhString?.filter((str) => typeof str == "string");
const isSearchStr = cleanedUpSearchStr?.[0] && cleanedUpSearchStr.find((str) => str);
if (isSearchStr) {
const stringOperator = genObject?.searchOperator || "AND";
queryString += ` WHERE ${cleanedUpSearchStr.join(` ${stringOperator} `)}`;
}
if (genObject?.fullTextSearch && fullTextSearchStr && fullTextMatchStr) {
queryString += `${isSearchStr ? " AND" : " WHERE"} ${fullTextMatchStr}`;
sqlSearhValues.push(fullTextSearchStr);
}
if (genObject?.group) {
let group_by_txt = ``;
if (typeof genObject.group == "string") {
group_by_txt = genObject.group;
}
else if (Array.isArray(genObject.group)) {
for (let i = 0; i < genObject.group.length; i++) {
const group = genObject.group[i];
if (typeof group == "string") {
group_by_txt += `\`${group.toString()}\``;
}
else if (typeof group == "object" && group.table) {
group_by_txt += `${group.table}.${String(group.field)}`;
}
else if (typeof group == "object") {
group_by_txt += `${String(group.field)}`;
}
if (i < genObject.group.length - 1) {
group_by_txt += ",";
}
}
}
else if (typeof genObject.group == "object") {
if (genObject.group.table) {
group_by_txt = `${genObject.group.table}.${String(genObject.group.field)}`;
}
else {
group_by_txt = `${String(genObject.group.field)}`;
}
}
queryString += ` GROUP BY ${group_by_txt}`;
}
function grabOrderString(order) {
let orderFields = [];
let orderSrt = ``;
if (genObject?.fullTextSearch && genObject.fullTextSearch.scoreAlias) {
orderFields.push(genObject.fullTextSearch.scoreAlias);
}
else if (genObject?.join) {
orderFields.push(`${finalDbName}${tableName}.${String(order.field)}`);
}
else {
orderFields.push(order.field);
}
orderSrt += ` ${orderFields.join(", ")} ${order.strategy}`;
return orderSrt;
}
if (genObject?.order && !count) {
let orderSrt = ` ORDER BY`;
if (Array.isArray(genObject.order)) {
for (let i = 0; i < genObject.order.length; i++) {
const order = genObject.order[i];
if (order) {
orderSrt +=
grabOrderString(order) +
(i < genObject.order.length - 1 ? `,` : "");
}
}
}
else {
orderSrt += grabOrderString(genObject.order);
}
queryString += ` ${orderSrt}`;
}
if (genObject?.limit && !count)
queryString += ` LIMIT ${genObject.limit}`;
if (genObject?.offset && !count)
queryString += ` OFFSET ${genObject.offset}`;
return {
string: queryString,
values: sqlSearhValues,
};
}

5
dist/utils/sql-insert-generator.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import type { SQLInsertGenParams, SQLInsertGenReturn } from "../types";
/**
* # SQL Insert Generator
*/
export default function sqlInsertGenerator({ tableName, data, dbFullName, }: SQLInsertGenParams): SQLInsertGenReturn | undefined;

56
dist/utils/sql-insert-generator.js vendored Normal file
View File

@ -0,0 +1,56 @@
/**
* # SQL Insert Generator
*/
export default function sqlInsertGenerator({ tableName, data, dbFullName, }) {
const finalDbName = dbFullName ? `${dbFullName}.` : "";
try {
if (Array.isArray(data) && data?.[0]) {
let insertKeys = [];
data.forEach((dt) => {
const kys = Object.keys(dt);
kys.forEach((ky) => {
if (!insertKeys.includes(ky)) {
insertKeys.push(ky);
}
});
});
let queryBatches = [];
let queryValues = [];
data.forEach((item) => {
queryBatches.push(`(${insertKeys
.map((ky) => {
const value = item[ky];
const finalValue = typeof value == "string" ||
typeof value == "number"
? value
: value
? String(value().value)
: null;
if (!finalValue) {
queryValues.push("");
return "?";
}
queryValues.push(finalValue);
const placeholder = typeof value == "function"
? value().placeholder
: "?";
return placeholder;
})
.filter((k) => Boolean(k))
.join(",")})`);
});
let query = `INSERT INTO ${finalDbName}${tableName} (${insertKeys.join(",")}) VALUES ${queryBatches.join(",")}`;
return {
query: query,
values: queryValues,
};
}
else {
return undefined;
}
}
catch ( /** @type {any} */error) {
console.log(`SQL insert gen ERROR: ${error.message}`);
return undefined;
}
}

View File

@ -2,6 +2,7 @@
"name": "@moduletrace/bun-sqlite", "name": "@moduletrace/bun-sqlite",
"version": "1.0.0", "version": "1.0.0",
"description": "SQLite manager for Bun", "description": "SQLite manager for Bun",
"author": "Benjamin Toby",
"main": "dist/index.js", "main": "dist/index.js",
"bin": { "bin": {
"bun-sqlite": "dist/commands/index.js" "bun-sqlite": "dist/commands/index.js"

View File

@ -7,9 +7,7 @@
"jsx": "react-jsx", "jsx": "react-jsx",
"allowJs": true, "allowJs": true,
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"noEmit": true,
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
@ -20,6 +18,9 @@
"noPropertyAccessFromIndexSignature": false, "noPropertyAccessFromIndexSignature": false,
"declaration": true, "declaration": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"maxNodeModuleJsDepth": 10,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"outDir": "dist" "outDir": "dist"
}, },
"include": ["src"], "include": ["src"],