bun-query-builder 0.1.20 → 0.1.23
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/dist/actions/data.d.ts +8 -0
- package/dist/bin/cli.js +198 -82
- package/dist/browser.d.ts +17 -6
- package/dist/drivers/mysql.d.ts +13 -1
- package/dist/drivers/postgres.d.ts +13 -1
- package/dist/drivers/sqlite.d.ts +13 -1
- package/dist/index.d.ts +5 -5
- package/dist/model.d.ts +12 -0
- package/dist/src/index.js +216 -87
- package/package.json +3 -3
- package/LICENSE.md +0 -21
package/dist/actions/data.d.ts
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
// eslint-disable-next-line pickier/no-unused-vars
|
|
5
5
|
export declare function exportData(tableName: string, options?: ExportOptions): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Import data into a table
|
|
8
|
+
*/
|
|
9
|
+
export declare function importData(tableName: string, filePath: string, options?: ImportOptions): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Dump database or specific tables to SQL
|
|
12
|
+
*/
|
|
13
|
+
export declare function dumpDatabase(options?: DumpOptions): Promise<void>;
|
|
6
14
|
export declare interface ExportOptions {
|
|
7
15
|
format?: 'json' | 'csv' | 'sql'
|
|
8
16
|
output?: string
|
package/dist/bin/cli.js
CHANGED
|
@@ -11663,9 +11663,6 @@ async function getConfig() {
|
|
|
11663
11663
|
return _config;
|
|
11664
11664
|
}
|
|
11665
11665
|
function setConfig(userConfig) {
|
|
11666
|
-
if (config5 === undefined || config5.dialect === undefined) {
|
|
11667
|
-
config5 = { ...defaultConfig4 };
|
|
11668
|
-
}
|
|
11669
11666
|
Object.assign(config5, userConfig);
|
|
11670
11667
|
if (userConfig.database) {
|
|
11671
11668
|
config5.database = { ...config5.database, ...userConfig.database };
|
|
@@ -12380,6 +12377,7 @@ function createQueryBuilder(state) {
|
|
|
12380
12377
|
function applyCondition(expr) {
|
|
12381
12378
|
if (Array.isArray(expr)) {
|
|
12382
12379
|
const [col, op, val] = expr;
|
|
12380
|
+
validateIdentifier(col, "where(column)");
|
|
12383
12381
|
const colName = String(col);
|
|
12384
12382
|
switch (op) {
|
|
12385
12383
|
case "in":
|
|
@@ -12397,9 +12395,12 @@ function createQueryBuilder(state) {
|
|
|
12397
12395
|
case "like":
|
|
12398
12396
|
return _sql.unsafe(`${colName} LIKE ${getPlaceholder(1)}`, [val]);
|
|
12399
12397
|
case "is":
|
|
12400
|
-
|
|
12401
|
-
|
|
12402
|
-
|
|
12398
|
+
case "is not": {
|
|
12399
|
+
if (val !== null && val !== undefined) {
|
|
12400
|
+
throw new TypeError(`[query-builder] where(..., '${op}', ?): operator '${op}' only accepts NULL/undefined as value, got ${typeof val} (${String(val)})`);
|
|
12401
|
+
}
|
|
12402
|
+
return _sql.unsafe(`${colName} IS ${op === "is not" ? "NOT " : ""}NULL`);
|
|
12403
|
+
}
|
|
12403
12404
|
case "!=":
|
|
12404
12405
|
return _sql.unsafe(`${colName} <> ${getPlaceholder(1)}`, [val]);
|
|
12405
12406
|
case "<":
|
|
@@ -12407,8 +12408,9 @@ function createQueryBuilder(state) {
|
|
|
12407
12408
|
case "<=":
|
|
12408
12409
|
case ">=":
|
|
12409
12410
|
case "=":
|
|
12410
|
-
default:
|
|
12411
12411
|
return _sql.unsafe(`${colName} ${op} ${getPlaceholder(1)}`, [val]);
|
|
12412
|
+
default:
|
|
12413
|
+
throw new TypeError(`[query-builder] where(..., '${String(op)}', ?): unsupported operator. Allowed: =, !=, <>, <, <=, >, >=, like, in, not in, is, is not`);
|
|
12412
12414
|
}
|
|
12413
12415
|
}
|
|
12414
12416
|
if ("raw" in expr) {
|
|
@@ -12421,6 +12423,7 @@ function createQueryBuilder(state) {
|
|
|
12421
12423
|
const allParams = [];
|
|
12422
12424
|
let paramIndex = 1;
|
|
12423
12425
|
for (const key of keys) {
|
|
12426
|
+
validateIdentifier(key, "where(object key)");
|
|
12424
12427
|
const value = expr[key];
|
|
12425
12428
|
if (Array.isArray(value)) {
|
|
12426
12429
|
const placeholders = getPlaceholders(value.length, paramIndex);
|
|
@@ -12609,17 +12612,17 @@ function createQueryBuilder(state) {
|
|
|
12609
12612
|
return;
|
|
12610
12613
|
const parentTable = String(table);
|
|
12611
12614
|
const parentPk = meta.primaryKeys[parentTable] ?? "id";
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12615
|
+
validateIdentifier2(resolved.pivotTable, "wherePivot auto-join (pivot table)");
|
|
12616
|
+
validateIdentifier2(resolved.fkParent, "wherePivot auto-join (parent FK)");
|
|
12617
|
+
validateIdentifier2(parentTable, "wherePivot auto-join (parent table)");
|
|
12618
|
+
validateIdentifier2(parentPk, "wherePivot auto-join (parent PK)");
|
|
12616
12619
|
built = sql`${ensureBuilt()} LEFT JOIN ${sql(resolved.pivotTable)} ON ${sql(`${resolved.pivotTable}.${resolved.fkParent}`)} = ${sql(`${parentTable}.${parentPk}`)}`;
|
|
12617
12620
|
text = `${text} LEFT JOIN ${resolved.pivotTable} ON ${resolved.pivotTable}.${resolved.fkParent} = ${parentTable}.${parentPk}`;
|
|
12618
12621
|
joinedTables.add(resolved.pivotTable);
|
|
12619
12622
|
};
|
|
12620
12623
|
const whereConditions = [];
|
|
12621
12624
|
const whereParams = [];
|
|
12622
|
-
const
|
|
12625
|
+
const validateIdentifier2 = (name, context) => {
|
|
12623
12626
|
if (!SQL_PATTERNS.IDENTIFIER.test(name)) {
|
|
12624
12627
|
const contextMsg = context ? ` in ${context}` : "";
|
|
12625
12628
|
throw new Error(`[query-builder] Invalid identifier${contextMsg}: '${name}'. Identifiers must start with a letter or underscore and contain only alphanumeric characters, underscores, and dots.`);
|
|
@@ -12643,18 +12646,56 @@ function createQueryBuilder(state) {
|
|
|
12643
12646
|
}
|
|
12644
12647
|
}
|
|
12645
12648
|
};
|
|
12649
|
+
const SAFE_WHERE_OPERATORS = new Set([
|
|
12650
|
+
"=",
|
|
12651
|
+
"!=",
|
|
12652
|
+
"<>",
|
|
12653
|
+
"<",
|
|
12654
|
+
"<=",
|
|
12655
|
+
">",
|
|
12656
|
+
">=",
|
|
12657
|
+
"like",
|
|
12658
|
+
"not like",
|
|
12659
|
+
"ilike",
|
|
12660
|
+
"not ilike",
|
|
12661
|
+
"in",
|
|
12662
|
+
"not in",
|
|
12663
|
+
"is",
|
|
12664
|
+
"is not",
|
|
12665
|
+
"between",
|
|
12666
|
+
"not between"
|
|
12667
|
+
]);
|
|
12668
|
+
function assertSafeWhereOperator(op, context) {
|
|
12669
|
+
if (typeof op !== "string")
|
|
12670
|
+
throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
|
|
12671
|
+
const lower = op.toLowerCase();
|
|
12672
|
+
if (!SAFE_WHERE_OPERATORS.has(lower))
|
|
12673
|
+
throw new TypeError(`[query-builder] ${context}: refusing to use '${op}' as a SQL operator \u2014 not in the allowed set (${[...SAFE_WHERE_OPERATORS].join(", ")})`);
|
|
12674
|
+
return op;
|
|
12675
|
+
}
|
|
12676
|
+
function formatSubqueryValue(val) {
|
|
12677
|
+
if (val === null)
|
|
12678
|
+
return "NULL";
|
|
12679
|
+
if (typeof val === "number" && Number.isFinite(val))
|
|
12680
|
+
return String(val);
|
|
12681
|
+
if (typeof val === "boolean")
|
|
12682
|
+
return val ? "1" : "0";
|
|
12683
|
+
if (typeof val === "string")
|
|
12684
|
+
return `'${val.replace(/'/g, "''")}'`;
|
|
12685
|
+
throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
|
|
12686
|
+
}
|
|
12646
12687
|
const buildHasSubquery = (parentTable, targetTable, pk, callback) => {
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12688
|
+
validateIdentifier2(parentTable, "relationship subquery (parent table)");
|
|
12689
|
+
validateIdentifier2(targetTable, "relationship subquery (target table)");
|
|
12690
|
+
validateIdentifier2(pk, "relationship subquery (primary key)");
|
|
12650
12691
|
const fk = `${parentTable.endsWith("s") ? parentTable.slice(0, -1) : parentTable}_id`;
|
|
12651
|
-
|
|
12692
|
+
validateIdentifier2(fk, "relationship subquery (foreign key)");
|
|
12652
12693
|
let subquerySQL = `SELECT 1 FROM ${targetTable} WHERE ${targetTable}.${fk} = ${parentTable}.${pk}`;
|
|
12653
12694
|
if (callback) {
|
|
12654
12695
|
const subQb = {
|
|
12655
12696
|
where: (col, op, val) => {
|
|
12656
|
-
|
|
12657
|
-
return `${targetTable}.${col} ${op
|
|
12697
|
+
validateIdentifier2(col, "relationship subquery condition");
|
|
12698
|
+
return `${targetTable}.${col} ${assertSafeWhereOperator(op, "whereHas callback")} ${formatSubqueryValue(val)}`;
|
|
12658
12699
|
}
|
|
12659
12700
|
};
|
|
12660
12701
|
const condition = callback(subQb);
|
|
@@ -12665,16 +12706,16 @@ function createQueryBuilder(state) {
|
|
|
12665
12706
|
return subquerySQL;
|
|
12666
12707
|
};
|
|
12667
12708
|
const buildBelongsToSubquery = (parentTable, targetTable, pk, callback) => {
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12709
|
+
validateIdentifier2(parentTable, "relationship subquery (parent table)");
|
|
12710
|
+
validateIdentifier2(targetTable, "relationship subquery (target table)");
|
|
12711
|
+
validateIdentifier2(pk, "relationship subquery (primary key)");
|
|
12671
12712
|
const fk = `${targetTable.endsWith("s") ? targetTable.slice(0, -1) : targetTable}_id`;
|
|
12672
|
-
|
|
12713
|
+
validateIdentifier2(fk, "relationship subquery (foreign key)");
|
|
12673
12714
|
let subquerySQL = `SELECT 1 FROM ${targetTable} WHERE ${targetTable}.${pk} = ${parentTable}.${fk}`;
|
|
12674
12715
|
if (callback) {
|
|
12675
12716
|
const subQb = {
|
|
12676
12717
|
where: (col, op, val) => {
|
|
12677
|
-
|
|
12718
|
+
validateIdentifier2(col, "relationship subquery condition");
|
|
12678
12719
|
return `${targetTable}.${col} ${op} ${typeof val === "string" ? `'${val}'` : val}`;
|
|
12679
12720
|
}
|
|
12680
12721
|
};
|
|
@@ -12686,24 +12727,24 @@ function createQueryBuilder(state) {
|
|
|
12686
12727
|
return subquerySQL;
|
|
12687
12728
|
};
|
|
12688
12729
|
const buildBelongsToManySubquery = (parentTable, targetTable, pk, targetPk, callback, relationKey) => {
|
|
12689
|
-
|
|
12690
|
-
|
|
12691
|
-
|
|
12692
|
-
|
|
12730
|
+
validateIdentifier2(parentTable, "relationship subquery (parent table)");
|
|
12731
|
+
validateIdentifier2(targetTable, "relationship subquery (target table)");
|
|
12732
|
+
validateIdentifier2(pk, "relationship subquery (primary key)");
|
|
12733
|
+
validateIdentifier2(targetPk, "relationship subquery (target primary key)");
|
|
12693
12734
|
const resolved = relationKey && meta ? resolvePivot(meta, parentTable, relationKey, { singularize, models: meta.models }) : null;
|
|
12694
12735
|
const a = singularize(parentTable);
|
|
12695
12736
|
const b = singularize(targetTable);
|
|
12696
12737
|
const pivot = resolved?.pivotTable ?? [a, b].sort().join("_");
|
|
12697
12738
|
const fkA = resolved?.fkParent ?? `${a}_id`;
|
|
12698
12739
|
const fkB = resolved?.fkRelated ?? `${b}_id`;
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12740
|
+
validateIdentifier2(pivot, "relationship subquery (pivot table)");
|
|
12741
|
+
validateIdentifier2(fkA, "relationship subquery (foreign key A)");
|
|
12742
|
+
validateIdentifier2(fkB, "relationship subquery (foreign key B)");
|
|
12702
12743
|
let subquerySQL = `SELECT 1 FROM ${pivot} JOIN ${targetTable} ON ${targetTable}.${targetPk} = ${pivot}.${fkB} WHERE ${pivot}.${fkA} = ${parentTable}.${pk}`;
|
|
12703
12744
|
if (callback) {
|
|
12704
12745
|
const subQb = {
|
|
12705
12746
|
where: (col, op, val) => {
|
|
12706
|
-
|
|
12747
|
+
validateIdentifier2(col, "relationship subquery condition");
|
|
12707
12748
|
return `${targetTable}.${col} ${op} ${typeof val === "string" ? `'${val}'` : val}`;
|
|
12708
12749
|
}
|
|
12709
12750
|
};
|
|
@@ -12715,24 +12756,24 @@ function createQueryBuilder(state) {
|
|
|
12715
12756
|
return subquerySQL;
|
|
12716
12757
|
};
|
|
12717
12758
|
const buildHasCountSubquery = (parentTable, targetTable, pk) => {
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12759
|
+
validateIdentifier2(parentTable, "withCount (parent table)");
|
|
12760
|
+
validateIdentifier2(targetTable, "withCount (target table)");
|
|
12761
|
+
validateIdentifier2(pk, "withCount (primary key)");
|
|
12721
12762
|
const fk = `${parentTable.endsWith("s") ? parentTable.slice(0, -1) : parentTable}_id`;
|
|
12722
|
-
|
|
12763
|
+
validateIdentifier2(fk, "withCount (foreign key)");
|
|
12723
12764
|
return `(SELECT COUNT(*) FROM ${targetTable} WHERE ${targetTable}.${fk} = ${parentTable}.${pk})`;
|
|
12724
12765
|
};
|
|
12725
12766
|
const buildBelongsToManyCountSubquery = (parentTable, targetTable, pk, relationKey) => {
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
|
|
12767
|
+
validateIdentifier2(parentTable, "withCount (parent table)");
|
|
12768
|
+
validateIdentifier2(targetTable, "withCount (target table)");
|
|
12769
|
+
validateIdentifier2(pk, "withCount (primary key)");
|
|
12729
12770
|
const resolved = relationKey && meta ? resolvePivot(meta, parentTable, relationKey, { singularize, models: meta.models }) : null;
|
|
12730
12771
|
const a = singularize(parentTable);
|
|
12731
12772
|
const b = singularize(targetTable);
|
|
12732
12773
|
const pivot = resolved?.pivotTable ?? [a, b].sort().join("_");
|
|
12733
12774
|
const fkA = resolved?.fkParent ?? `${a}_id`;
|
|
12734
|
-
|
|
12735
|
-
|
|
12775
|
+
validateIdentifier2(pivot, "withCount (pivot table)");
|
|
12776
|
+
validateIdentifier2(fkA, "withCount (foreign key)");
|
|
12736
12777
|
return `(SELECT COUNT(*) FROM ${pivot} WHERE ${pivot}.${fkA} = ${parentTable}.${pk})`;
|
|
12737
12778
|
};
|
|
12738
12779
|
const applyPivotColumnsToQuery = () => {
|
|
@@ -12744,7 +12785,7 @@ function createQueryBuilder(state) {
|
|
|
12744
12785
|
if (!resolved)
|
|
12745
12786
|
continue;
|
|
12746
12787
|
for (const col of columns2) {
|
|
12747
|
-
|
|
12788
|
+
validateIdentifier2(col, "withPivot");
|
|
12748
12789
|
}
|
|
12749
12790
|
const pivotColumnsStr = columns2.map((col) => `${resolved.pivotTable}.${col} AS pivot_${col}`);
|
|
12750
12791
|
allPivotColumns.push(...pivotColumnsStr);
|
|
@@ -13294,8 +13335,8 @@ function createQueryBuilder(state) {
|
|
|
13294
13335
|
if (!resolved) {
|
|
13295
13336
|
throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
|
|
13296
13337
|
}
|
|
13297
|
-
|
|
13298
|
-
|
|
13338
|
+
validateIdentifier2(resolved.pivotTable, "wherePivot (pivot table)");
|
|
13339
|
+
validateIdentifier2(column, "wherePivot (column)");
|
|
13299
13340
|
ensurePivotJoined(resolved);
|
|
13300
13341
|
const op = value === undefined ? "=" : String(opOrValue);
|
|
13301
13342
|
const val = value === undefined ? opOrValue : value;
|
|
@@ -13317,8 +13358,8 @@ function createQueryBuilder(state) {
|
|
|
13317
13358
|
if (!resolved) {
|
|
13318
13359
|
throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
|
|
13319
13360
|
}
|
|
13320
|
-
|
|
13321
|
-
|
|
13361
|
+
validateIdentifier2(resolved.pivotTable, "wherePivotIn (pivot table)");
|
|
13362
|
+
validateIdentifier2(column, "wherePivotIn (column)");
|
|
13322
13363
|
ensurePivotJoined(resolved);
|
|
13323
13364
|
const placeholders = getPlaceholders(values.length, whereParams.length + 1);
|
|
13324
13365
|
const clause = `${resolved.pivotTable}.${column} IN (${placeholders})`;
|
|
@@ -13338,8 +13379,8 @@ function createQueryBuilder(state) {
|
|
|
13338
13379
|
if (!resolved) {
|
|
13339
13380
|
throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
|
|
13340
13381
|
}
|
|
13341
|
-
|
|
13342
|
-
|
|
13382
|
+
validateIdentifier2(resolved.pivotTable, "wherePivotNotIn (pivot table)");
|
|
13383
|
+
validateIdentifier2(column, "wherePivotNotIn (column)");
|
|
13343
13384
|
ensurePivotJoined(resolved);
|
|
13344
13385
|
const placeholders = getPlaceholders(values.length, whereParams.length + 1);
|
|
13345
13386
|
const clause = `${resolved.pivotTable}.${column} NOT IN (${placeholders})`;
|
|
@@ -13359,8 +13400,8 @@ function createQueryBuilder(state) {
|
|
|
13359
13400
|
if (!resolved) {
|
|
13360
13401
|
throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
|
|
13361
13402
|
}
|
|
13362
|
-
|
|
13363
|
-
|
|
13403
|
+
validateIdentifier2(resolved.pivotTable, "wherePivotNull (pivot table)");
|
|
13404
|
+
validateIdentifier2(column, "wherePivotNull (column)");
|
|
13364
13405
|
ensurePivotJoined(resolved);
|
|
13365
13406
|
const clause = `${resolved.pivotTable}.${column} IS NULL`;
|
|
13366
13407
|
whereConditions.push(clause);
|
|
@@ -13378,8 +13419,8 @@ function createQueryBuilder(state) {
|
|
|
13378
13419
|
if (!resolved) {
|
|
13379
13420
|
throw new Error(`[query-builder] Relationship '${relation}' is not a belongsToMany relationship on table '${String(table)}'`);
|
|
13380
13421
|
}
|
|
13381
|
-
|
|
13382
|
-
|
|
13422
|
+
validateIdentifier2(resolved.pivotTable, "wherePivotNotNull (pivot table)");
|
|
13423
|
+
validateIdentifier2(column, "wherePivotNotNull (column)");
|
|
13383
13424
|
ensurePivotJoined(resolved);
|
|
13384
13425
|
const clause = `${resolved.pivotTable}.${column} IS NOT NULL`;
|
|
13385
13426
|
whereConditions.push(clause);
|
|
@@ -13612,10 +13653,14 @@ function createQueryBuilder(state) {
|
|
|
13612
13653
|
return this;
|
|
13613
13654
|
},
|
|
13614
13655
|
whereDate(column, op, date) {
|
|
13656
|
+
validateIdentifier2(column, "whereDate(column)");
|
|
13657
|
+
const dateString = date instanceof Date ? date.toISOString() : typeof date === "string" ? date : (() => {
|
|
13658
|
+
throw new TypeError(`[query-builder] whereDate(date): expected string or Date, got ${typeof date}`);
|
|
13659
|
+
})();
|
|
13615
13660
|
const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
13616
13661
|
const idx = whereParams.length + 1;
|
|
13617
13662
|
text += ` ${keyword} ${column} ${op} ${getPlaceholder(idx)}`;
|
|
13618
|
-
whereParams.push(
|
|
13663
|
+
whereParams.push(dateString);
|
|
13619
13664
|
built = null;
|
|
13620
13665
|
return this;
|
|
13621
13666
|
},
|
|
@@ -13626,12 +13671,16 @@ function createQueryBuilder(state) {
|
|
|
13626
13671
|
return this;
|
|
13627
13672
|
},
|
|
13628
13673
|
whereColumn(left, op, right) {
|
|
13674
|
+
validateIdentifier2(left, "whereColumn(left)");
|
|
13675
|
+
validateIdentifier2(right, "whereColumn(right)");
|
|
13629
13676
|
const keyword = SQL_PATTERNS.WHERE.test(text) ? "AND" : "WHERE";
|
|
13630
13677
|
text += ` ${keyword} ${left} ${op} ${right}`;
|
|
13631
13678
|
built = null;
|
|
13632
13679
|
return this;
|
|
13633
13680
|
},
|
|
13634
13681
|
orWhereColumn(left, op, right) {
|
|
13682
|
+
validateIdentifier2(left, "orWhereColumn(left)");
|
|
13683
|
+
validateIdentifier2(right, "orWhereColumn(right)");
|
|
13635
13684
|
text += ` OR ${left} ${op} ${right}`;
|
|
13636
13685
|
built = null;
|
|
13637
13686
|
return this;
|
|
@@ -13851,11 +13900,15 @@ function createQueryBuilder(state) {
|
|
|
13851
13900
|
return this;
|
|
13852
13901
|
},
|
|
13853
13902
|
limit(n) {
|
|
13903
|
+
if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n))
|
|
13904
|
+
throw new TypeError(`[bun-query-builder] limit(n): expected non-negative integer, got ${n}`);
|
|
13854
13905
|
text = SQL_PATTERNS.LIMIT.test(text) ? text.replace(SQL_PATTERNS.LIMIT, ` LIMIT ${n}`) : `${text} LIMIT ${n}`;
|
|
13855
13906
|
built = null;
|
|
13856
13907
|
return this;
|
|
13857
13908
|
},
|
|
13858
13909
|
offset(n) {
|
|
13910
|
+
if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n))
|
|
13911
|
+
throw new TypeError(`[bun-query-builder] offset(n): expected non-negative integer, got ${n}`);
|
|
13859
13912
|
text = SQL_PATTERNS.OFFSET.test(text) ? text.replace(SQL_PATTERNS.OFFSET, ` OFFSET ${n}`) : `${text} OFFSET ${n}`;
|
|
13860
13913
|
built = null;
|
|
13861
13914
|
return this;
|
|
@@ -14128,19 +14181,25 @@ function createQueryBuilder(state) {
|
|
|
14128
14181
|
includeTrashed = true;
|
|
14129
14182
|
onlyTrashed = true;
|
|
14130
14183
|
const softDeleteColumn = config5.softDeletes?.column || "deleted_at";
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
14184
|
+
const splice = (raw, predicate2) => {
|
|
14185
|
+
const upper = raw.toUpperCase();
|
|
14186
|
+
let depth = 0;
|
|
14187
|
+
for (let i = 0;i < raw.length; i++) {
|
|
14188
|
+
const c = raw[i];
|
|
14189
|
+
if (c === "(")
|
|
14190
|
+
depth++;
|
|
14191
|
+
else if (c === ")")
|
|
14192
|
+
depth--;
|
|
14193
|
+
else if (depth === 0 && upper.substring(i, i + 5) === "WHERE" && (i === 0 || /\s/.test(raw[i - 1] ?? "")) && /\s/.test(raw[i + 5] ?? "")) {
|
|
14194
|
+
return `${raw.substring(0, i)}WHERE ${predicate2} AND ${raw.substring(i + 6)}`;
|
|
14195
|
+
}
|
|
14196
|
+
}
|
|
14197
|
+
return `${raw} WHERE ${predicate2}`;
|
|
14198
|
+
};
|
|
14199
|
+
const predicate = `${table}.${softDeleteColumn} IS NOT NULL`;
|
|
14200
|
+
text = splice(text, predicate);
|
|
14136
14201
|
const currentSql = String(ensureBuilt());
|
|
14137
|
-
|
|
14138
|
-
const newSql = currentSql.replace(/WHERE/, `WHERE ${table}.${softDeleteColumn} IS NOT NULL AND`);
|
|
14139
|
-
built = sql([newSql]);
|
|
14140
|
-
} else {
|
|
14141
|
-
const newSql = `${currentSql} WHERE ${table}.${softDeleteColumn} IS NOT NULL`;
|
|
14142
|
-
built = sql([newSql]);
|
|
14143
|
-
}
|
|
14202
|
+
built = sql([splice(currentSql, predicate)]);
|
|
14144
14203
|
return this;
|
|
14145
14204
|
},
|
|
14146
14205
|
scope(name, value) {
|
|
@@ -14314,7 +14373,15 @@ function createQueryBuilder(state) {
|
|
|
14314
14373
|
},
|
|
14315
14374
|
async count() {
|
|
14316
14375
|
const fromIdx = text.indexOf(" FROM ");
|
|
14317
|
-
const
|
|
14376
|
+
const hasGroupBy = / GROUP BY /i.test(text);
|
|
14377
|
+
let countText;
|
|
14378
|
+
if (hasGroupBy) {
|
|
14379
|
+
countText = `SELECT COUNT(*) as c FROM (${text}) AS _bqb_count_sub`;
|
|
14380
|
+
} else if (fromIdx !== -1) {
|
|
14381
|
+
countText = `SELECT COUNT(*) as c${text.substring(fromIdx)}`;
|
|
14382
|
+
} else {
|
|
14383
|
+
countText = `SELECT COUNT(*) as c FROM ${table}`;
|
|
14384
|
+
}
|
|
14318
14385
|
const cHooks = config5.hooks;
|
|
14319
14386
|
const cHasHooks = cHooks && (cHooks.onQueryStart || cHooks.onQueryEnd || cHooks.onQueryError || cHooks.startSpan);
|
|
14320
14387
|
if (!config5.softDeletes?.enabled && !useCache && !timeoutMs && !abortSignal && !cHasHooks) {
|
|
@@ -14738,7 +14805,7 @@ function createQueryBuilder(state) {
|
|
|
14738
14805
|
let sqlText = "";
|
|
14739
14806
|
const params = [];
|
|
14740
14807
|
const isPostgres = config5.dialect === "postgres";
|
|
14741
|
-
const quoteId = isPostgres ? (id) => `"${id}"` : config5.dialect === "mysql" ? (id) => `\`${id}\`` : (id) => id
|
|
14808
|
+
const quoteId = isPostgres ? (id) => `"${String(id).replace(/"/g, '""')}"` : config5.dialect === "mysql" ? (id) => `\`${String(id).replace(/`/g, "``")}\`` : (id) => `"${String(id).replace(/"/g, '""')}"`;
|
|
14742
14809
|
const getPlaceholder2 = isPostgres ? (index) => `$${index + 1}` : (_index) => "?";
|
|
14743
14810
|
return {
|
|
14744
14811
|
values(data) {
|
|
@@ -14871,11 +14938,11 @@ function createQueryBuilder(state) {
|
|
|
14871
14938
|
updateTable(table) {
|
|
14872
14939
|
let built;
|
|
14873
14940
|
const params = [];
|
|
14874
|
-
const quoteId = (
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
return `"${
|
|
14941
|
+
const quoteId = (identifier) => {
|
|
14942
|
+
const s = String(identifier);
|
|
14943
|
+
if (config5.dialect === "mysql")
|
|
14944
|
+
return `\`${s.replace(/`/g, "``")}\``;
|
|
14945
|
+
return `"${s.replace(/"/g, '""')}"`;
|
|
14879
14946
|
};
|
|
14880
14947
|
let sqlText = `UPDATE ${quoteId(String(table))}`;
|
|
14881
14948
|
return {
|
|
@@ -14979,11 +15046,11 @@ function createQueryBuilder(state) {
|
|
|
14979
15046
|
};
|
|
14980
15047
|
},
|
|
14981
15048
|
deleteFrom(table) {
|
|
14982
|
-
const quoteId = (
|
|
14983
|
-
|
|
14984
|
-
|
|
14985
|
-
|
|
14986
|
-
return `"${
|
|
15049
|
+
const quoteId = (identifier) => {
|
|
15050
|
+
const s = String(identifier);
|
|
15051
|
+
if (config5.dialect === "mysql")
|
|
15052
|
+
return `\`${s.replace(/`/g, "``")}\``;
|
|
15053
|
+
return `"${s.replace(/"/g, '""')}"`;
|
|
14987
15054
|
};
|
|
14988
15055
|
const quotedTable = quoteId(String(table));
|
|
14989
15056
|
let sqlText = `DELETE FROM ${quotedTable}`;
|
|
@@ -15267,6 +15334,10 @@ function createQueryBuilder(state) {
|
|
|
15267
15334
|
};
|
|
15268
15335
|
},
|
|
15269
15336
|
async insertOrIgnore(table, values) {
|
|
15337
|
+
if (config5.dialect === "mysql") {
|
|
15338
|
+
const built2 = bunSql`INSERT IGNORE INTO ${bunSql(String(table))} ${bunSql(values)}`;
|
|
15339
|
+
return built2.execute();
|
|
15340
|
+
}
|
|
15270
15341
|
const built = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(values)} ON CONFLICT DO NOTHING`;
|
|
15271
15342
|
return built.execute();
|
|
15272
15343
|
},
|
|
@@ -15302,7 +15373,15 @@ function createQueryBuilder(state) {
|
|
|
15302
15373
|
async upsert(table, rows, conflictColumns, mergeColumns) {
|
|
15303
15374
|
const targetCols = conflictColumns.map((c) => String(c));
|
|
15304
15375
|
const setCols = (mergeColumns ?? []).map((c) => String(c));
|
|
15305
|
-
|
|
15376
|
+
if (config5.dialect === "mysql") {
|
|
15377
|
+
const updateList2 = setCols.map((c) => `\`${c.replace(/`/g, "``")}\` = VALUES(\`${c.replace(/`/g, "``")}\`)`).join(", ");
|
|
15378
|
+
const built2 = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(rows)} ON DUPLICATE KEY UPDATE ${bunSql.unsafe(updateList2)}`;
|
|
15379
|
+
return built2.execute();
|
|
15380
|
+
}
|
|
15381
|
+
const isPostgres = config5.dialect === "postgres";
|
|
15382
|
+
const quoteCol = (column) => isPostgres ? `"${column.replace(/"/g, '""')}"` : `"${column.replace(/"/g, '""')}"`;
|
|
15383
|
+
const updateList = setCols.map((column) => `${quoteCol(column)} = EXCLUDED.${quoteCol(column)}`).join(", ");
|
|
15384
|
+
const built = bunSql`INSERT INTO ${bunSql(String(table))} ${bunSql(rows)} ON CONFLICT (${bunSql(targetCols)}) DO UPDATE SET ${bunSql.unsafe(updateList)}`;
|
|
15306
15385
|
return built.execute();
|
|
15307
15386
|
},
|
|
15308
15387
|
async save(table, values) {
|
|
@@ -24067,6 +24146,14 @@ var init_src = __esm(async () => {
|
|
|
24067
24146
|
|
|
24068
24147
|
// src/orm.ts
|
|
24069
24148
|
import { Database as Database2 } from "bun:sqlite";
|
|
24149
|
+
function assertValidIdentifier(name, context) {
|
|
24150
|
+
if (typeof name !== "string" || name.length === 0)
|
|
24151
|
+
throw new TypeError(`[bun-query-builder] ${context}: identifier must be a non-empty string, got ${typeof name}`);
|
|
24152
|
+
if (name.length > 64)
|
|
24153
|
+
throw new TypeError(`[bun-query-builder] ${context}: identifier '${name}' exceeds 64 chars`);
|
|
24154
|
+
if (!SAFE_SQL_IDENTIFIER.test(name))
|
|
24155
|
+
throw new TypeError(`[bun-query-builder] ${context}: identifier '${name}' contains characters outside [A-Za-z0-9_] \u2014 refusing to interpolate into SQL`);
|
|
24156
|
+
}
|
|
24070
24157
|
function getModelFromRegistry(name) {
|
|
24071
24158
|
if (!_getModel) {
|
|
24072
24159
|
try {
|
|
@@ -24617,14 +24704,21 @@ class BelongsToManyRelationBuilder {
|
|
|
24617
24704
|
return this;
|
|
24618
24705
|
}
|
|
24619
24706
|
orderBy(column, direction = "asc") {
|
|
24707
|
+
assertValidIdentifier(column, "orderBy(column)");
|
|
24708
|
+
if (direction !== "asc" && direction !== "desc")
|
|
24709
|
+
throw new TypeError(`[bun-query-builder] orderBy(direction): expected 'asc' or 'desc', got '${direction}'`);
|
|
24620
24710
|
this._orderBy.push(`${column} ${direction.toUpperCase()}`);
|
|
24621
24711
|
return this;
|
|
24622
24712
|
}
|
|
24623
24713
|
limit(n2) {
|
|
24714
|
+
if (!Number.isFinite(n2) || n2 < 0 || !Number.isInteger(n2))
|
|
24715
|
+
throw new TypeError(`[bun-query-builder] limit(n): expected non-negative integer, got ${n2}`);
|
|
24624
24716
|
this._limit = n2;
|
|
24625
24717
|
return this;
|
|
24626
24718
|
}
|
|
24627
24719
|
offset(n2) {
|
|
24720
|
+
if (!Number.isFinite(n2) || n2 < 0 || !Number.isInteger(n2))
|
|
24721
|
+
throw new TypeError(`[bun-query-builder] offset(n): expected non-negative integer, got ${n2}`);
|
|
24628
24722
|
this._offset = n2;
|
|
24629
24723
|
return this;
|
|
24630
24724
|
}
|
|
@@ -24851,18 +24945,22 @@ class ModelQueryBuilder {
|
|
|
24851
24945
|
return this;
|
|
24852
24946
|
}
|
|
24853
24947
|
whereNull(column) {
|
|
24948
|
+
assertValidIdentifier(column, "whereNull(column)");
|
|
24854
24949
|
this._wheres.push({ column, operator: "=", value: null, boolean: "and" });
|
|
24855
24950
|
return this;
|
|
24856
24951
|
}
|
|
24857
24952
|
orWhereNull(column) {
|
|
24953
|
+
assertValidIdentifier(column, "orWhereNull(column)");
|
|
24858
24954
|
this._wheres.push({ column, operator: "=", value: null, boolean: "or" });
|
|
24859
24955
|
return this;
|
|
24860
24956
|
}
|
|
24861
24957
|
whereNotNull(column) {
|
|
24958
|
+
assertValidIdentifier(column, "whereNotNull(column)");
|
|
24862
24959
|
this._wheres.push({ column, operator: "!=", value: null, boolean: "and" });
|
|
24863
24960
|
return this;
|
|
24864
24961
|
}
|
|
24865
24962
|
orWhereNotNull(column) {
|
|
24963
|
+
assertValidIdentifier(column, "orWhereNotNull(column)");
|
|
24866
24964
|
this._wheres.push({ column, operator: "!=", value: null, boolean: "or" });
|
|
24867
24965
|
return this;
|
|
24868
24966
|
}
|
|
@@ -24909,13 +25007,19 @@ class ModelQueryBuilder {
|
|
|
24909
25007
|
return this;
|
|
24910
25008
|
}
|
|
24911
25009
|
whereBetween(column, range) {
|
|
25010
|
+
assertValidIdentifier(column, "whereBetween(column)");
|
|
24912
25011
|
this._wheres.push({ column, operator: ">=", value: range[0], boolean: "and" });
|
|
24913
25012
|
this._wheres.push({ column, operator: "<=", value: range[1], boolean: "and" });
|
|
24914
25013
|
return this;
|
|
24915
25014
|
}
|
|
24916
25015
|
whereNotBetween(column, range) {
|
|
24917
|
-
|
|
24918
|
-
|
|
25016
|
+
assertValidIdentifier(column, "whereNotBetween(column)");
|
|
25017
|
+
const col = column;
|
|
25018
|
+
this._wheres.push({
|
|
25019
|
+
raw: `(${col} < ? OR ${col} > ?)`,
|
|
25020
|
+
rawParams: [range[0], range[1]],
|
|
25021
|
+
boolean: "and"
|
|
25022
|
+
});
|
|
24919
25023
|
return this;
|
|
24920
25024
|
}
|
|
24921
25025
|
when(condition, callback) {
|
|
@@ -24925,6 +25029,9 @@ class ModelQueryBuilder {
|
|
|
24925
25029
|
return this;
|
|
24926
25030
|
}
|
|
24927
25031
|
orderBy(column, direction = "asc") {
|
|
25032
|
+
assertValidIdentifier(column, "orderBy(column)");
|
|
25033
|
+
if (direction !== "asc" && direction !== "desc")
|
|
25034
|
+
throw new TypeError(`[bun-query-builder] orderBy(direction): expected 'asc' or 'desc', got '${direction}'`);
|
|
24928
25035
|
this._orderBy.push({ column, direction });
|
|
24929
25036
|
return this;
|
|
24930
25037
|
}
|
|
@@ -25169,6 +25276,7 @@ class ModelQueryBuilder {
|
|
|
25169
25276
|
return results[0];
|
|
25170
25277
|
}
|
|
25171
25278
|
increment(column, amount = 1) {
|
|
25279
|
+
assertValidIdentifier(column, "increment(column)");
|
|
25172
25280
|
const db = getDatabase();
|
|
25173
25281
|
const params = [amount];
|
|
25174
25282
|
let sql2 = `UPDATE ${this._definition.table} SET ${column} = ${column} + ?`;
|
|
@@ -25225,6 +25333,7 @@ class ModelQueryBuilder {
|
|
|
25225
25333
|
};
|
|
25226
25334
|
}
|
|
25227
25335
|
pluck(column) {
|
|
25336
|
+
assertValidIdentifier(column, "pluck(column)");
|
|
25228
25337
|
const db = getDatabase();
|
|
25229
25338
|
const params = [];
|
|
25230
25339
|
let sql2 = `SELECT ${column} FROM ${this._definition.table}`;
|
|
@@ -25242,6 +25351,7 @@ class ModelQueryBuilder {
|
|
|
25242
25351
|
return rows.map((r2) => r2[column]);
|
|
25243
25352
|
}
|
|
25244
25353
|
aggregate(fn, column) {
|
|
25354
|
+
assertValidIdentifier(column, `${fn}(column)`);
|
|
25245
25355
|
const db = getDatabase();
|
|
25246
25356
|
const params = [];
|
|
25247
25357
|
let sql2 = `SELECT ${fn}(${column}) as v FROM ${this._definition.table}`;
|
|
@@ -25518,8 +25628,9 @@ async function seedModel(definition, count, faker) {
|
|
|
25518
25628
|
db.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
|
|
25519
25629
|
}
|
|
25520
25630
|
}
|
|
25521
|
-
var _getModel = null, globalDb = null, snakeCaseCache, tableNameCache, relationCache, preparedStatementCache;
|
|
25631
|
+
var SAFE_SQL_IDENTIFIER, _getModel = null, globalDb = null, snakeCaseCache, tableNameCache, relationCache, preparedStatementCache;
|
|
25522
25632
|
var init_orm = __esm(() => {
|
|
25633
|
+
SAFE_SQL_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/i;
|
|
25523
25634
|
snakeCaseCache = new Map;
|
|
25524
25635
|
tableNameCache = new Map;
|
|
25525
25636
|
relationCache = new Map;
|
|
@@ -25529,6 +25640,7 @@ var init_orm = __esm(() => {
|
|
|
25529
25640
|
// src/model.ts
|
|
25530
25641
|
var exports_model = {};
|
|
25531
25642
|
__export(exports_model, {
|
|
25643
|
+
registerModel: () => registerModel,
|
|
25532
25644
|
registerBrowserModels: () => registerBrowserModels,
|
|
25533
25645
|
hasModel: () => hasModel,
|
|
25534
25646
|
getModelRegistry: () => getModelRegistry,
|
|
@@ -25552,6 +25664,10 @@ function hasModel(name) {
|
|
|
25552
25664
|
function clearModelRegistry() {
|
|
25553
25665
|
modelRegistry.clear();
|
|
25554
25666
|
}
|
|
25667
|
+
function registerModel(name, model) {
|
|
25668
|
+
modelRegistry.set(name, model);
|
|
25669
|
+
return model;
|
|
25670
|
+
}
|
|
25555
25671
|
function isClientSide() {
|
|
25556
25672
|
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
25557
25673
|
}
|
|
@@ -25568,7 +25684,7 @@ function defineModel(definition) {
|
|
|
25568
25684
|
getName: () => definition.name
|
|
25569
25685
|
});
|
|
25570
25686
|
}
|
|
25571
|
-
|
|
25687
|
+
registerModel(definition.name, model);
|
|
25572
25688
|
return model;
|
|
25573
25689
|
}
|
|
25574
25690
|
function registerBrowserModels(models) {
|
|
@@ -29921,7 +30037,7 @@ function getPrefix() {
|
|
|
29921
30037
|
}
|
|
29922
30038
|
var prefix = getPrefix();
|
|
29923
30039
|
// package.json
|
|
29924
|
-
var version2 = "0.1.
|
|
30040
|
+
var version2 = "0.1.23";
|
|
29925
30041
|
|
|
29926
30042
|
// bin/cli.ts
|
|
29927
30043
|
init_actions();
|