bun-query-builder 0.2.41 → 0.2.44

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/bin/cli.js CHANGED
@@ -476,7 +476,7 @@ class BrowserModelQueryBuilder {
476
476
  for (const where of this._wheres) {
477
477
  if (where.operator === "=") {
478
478
  params.append(where.column, String(where.value));
479
- } else if ((where.operator === "in" || where.operator === "not in") && Array.isArray(where.value)) {
479
+ } else if (where.operator === "in" && Array.isArray(where.value)) {
480
480
  params.append(`${where.column}[]`, where.value.join(","));
481
481
  } else if (where.operator === "is" && where.value === null) {
482
482
  params.append(`filter[${where.column}][is]`, "null");
@@ -12856,7 +12856,10 @@ function warnOnceBareSqlFragment(context) {
12856
12856
  if (warnedSqlFragmentContexts.has(context))
12857
12857
  return;
12858
12858
  warnedSqlFragmentContexts.add(context);
12859
- console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer \`sql\`...\`\` tagged-template fragments so values are parameterised ` + `instead of concatenated \u2014 concatenating request input into SQL is an ` + `injection vector. This will become a hard error in a future release.`);
12859
+ console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer the \`raw\`...\`\` tagged template, which escapes interpolated ` + `values for the configured dialect \u2014 concatenating request input into SQL ` + `is an injection vector. This will become a hard error in a future release.`);
12860
+ }
12861
+ function escapeStringLiteral(value, dialect = config5.dialect) {
12862
+ return isMysqlLike(dialect) ? value.replace(/\\/g, "\\\\").replace(/'/g, "''") : value.replace(/'/g, "''");
12860
12863
  }
12861
12864
  function formatSubqueryValue(val) {
12862
12865
  if (val === null)
@@ -12866,7 +12869,7 @@ function formatSubqueryValue(val) {
12866
12869
  if (typeof val === "boolean")
12867
12870
  return val ? "1" : "0";
12868
12871
  if (typeof val === "string")
12869
- return `'${val.replace(/'/g, "''")}'`;
12872
+ return `'${escapeStringLiteral(val)}'`;
12870
12873
  if (val instanceof Date)
12871
12874
  return `'${val.toISOString()}'`;
12872
12875
  throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
@@ -14487,11 +14490,13 @@ function createQueryBuilder(state) {
14487
14490
  const dialect = config5.dialect;
14488
14491
  const idx = whereParams.length + 1;
14489
14492
  if (dialect === "postgres") {
14493
+ const needsJsonbLift = typeof json === "number" || typeof json === "boolean";
14494
+ const operand = needsJsonbLift ? `to_jsonb(${getPlaceholder(idx)})` : getPlaceholder(idx);
14490
14495
  if (config5.sql?.jsonContainsMode === "function")
14491
- pushWhere("AND", `jsonb_contains(${column}, ${getPlaceholder(idx)})`);
14496
+ pushWhere("AND", `jsonb_contains(${column}, ${operand})`);
14492
14497
  else
14493
- pushWhere("AND", `${column} @> ${getPlaceholder(idx)}`);
14494
- whereParams.push(JSON.stringify(json));
14498
+ pushWhere("AND", `${column} @> ${operand}`);
14499
+ whereParams.push(json);
14495
14500
  } else if (isMysqlLike(dialect)) {
14496
14501
  pushWhere("AND", `JSON_CONTAINS(${column}, ${getPlaceholder(idx)})`);
14497
14502
  whereParams.push(JSON.stringify(json));
@@ -15788,6 +15793,31 @@ function createQueryBuilder(state) {
15788
15793
  let built;
15789
15794
  let sqlText = "";
15790
15795
  const params = [];
15796
+ let noRows = false;
15797
+ const emptyInsertResult = () => isPostgres ? [] : { changes: 0, lastInsertRowid: 0 };
15798
+ const emptyReturningBuilder = () => {
15799
+ const noRow = async () => {
15800
+ return;
15801
+ };
15802
+ const noRowOrFail = async () => {
15803
+ throw new Error("Insert with RETURNING returned no rows");
15804
+ };
15805
+ return {
15806
+ where: () => emptyReturningBuilder(),
15807
+ andWhere: () => emptyReturningBuilder(),
15808
+ orWhere: () => emptyReturningBuilder(),
15809
+ orderBy: () => emptyReturningBuilder(),
15810
+ limit: () => emptyReturningBuilder(),
15811
+ offset: () => emptyReturningBuilder(),
15812
+ toSQL: () => makeExecutableQuery(null, ""),
15813
+ execute: async () => [],
15814
+ get: async () => [],
15815
+ first: noRow,
15816
+ executeTakeFirst: noRow,
15817
+ firstOrFail: noRowOrFail,
15818
+ executeTakeFirstOrThrow: noRowOrFail
15819
+ };
15820
+ };
15791
15821
  const isPostgres = config5.dialect === "postgres";
15792
15822
  const quoteId = isPostgres ? (id) => `"${String(id).replace(/"/g, '""')}"` : isMysqlLike(config5.dialect) ? (id) => `\`${String(id).replace(/`/g, "``")}\`` : (id) => `"${String(id).replace(/"/g, '""')}"`;
15793
15823
  const getPlaceholder2 = isPostgres ? (index) => `$${index + 1}` : (_index) => "?";
@@ -15796,9 +15826,13 @@ function createQueryBuilder(state) {
15796
15826
  const rows = Array.isArray(data) ? data : [data];
15797
15827
  const rowCount = rows.length;
15798
15828
  if (rowCount === 0) {
15799
- built = _sql.unsafe("SELECT 1");
15829
+ noRows = true;
15830
+ built = null;
15831
+ sqlText = "";
15832
+ params.length = 0;
15800
15833
  return this;
15801
15834
  }
15835
+ noRows = false;
15802
15836
  const firstRow = rows[0];
15803
15837
  const keys = Object.keys(firstRow);
15804
15838
  const colCount = keys.length;
@@ -15852,6 +15886,8 @@ function createQueryBuilder(state) {
15852
15886
  return this;
15853
15887
  },
15854
15888
  returning(...cols) {
15889
+ if (noRows)
15890
+ return emptyReturningBuilder();
15855
15891
  const returningSql = `${sqlText} RETURNING ${cols.join(", ")}`;
15856
15892
  const q = _sql.unsafe(returningSql, params);
15857
15893
  const runFirst = async () => {
@@ -15885,11 +15921,15 @@ function createQueryBuilder(state) {
15885
15921
  };
15886
15922
  },
15887
15923
  toSQL() {
15924
+ if (noRows)
15925
+ return makeExecutableQuery(null, "");
15888
15926
  if (!built)
15889
15927
  built = _sql.unsafe(sqlText, params);
15890
15928
  return makeExecutableQuery(built, sqlText);
15891
15929
  },
15892
15930
  execute() {
15931
+ if (noRows)
15932
+ return Promise.resolve(emptyInsertResult());
15893
15933
  const hooks = activeHooks();
15894
15934
  const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hooks.beforeCreate || hooks.afterCreate || hasSlowQueryHook(hooks));
15895
15935
  if (!hasHooks) {
@@ -15904,12 +15944,16 @@ function createQueryBuilder(state) {
15904
15944
  return runWithHooks(built, "insert");
15905
15945
  },
15906
15946
  async executeTakeFirst() {
15947
+ if (noRows)
15948
+ return emptyInsertResult();
15907
15949
  if (!built)
15908
15950
  built = _sql.unsafe(sqlText, params);
15909
15951
  const result = await runWithHooks(built, "insert");
15910
15952
  return result;
15911
15953
  },
15912
15954
  async executeTakeFirstOrThrow() {
15955
+ if (noRows)
15956
+ throw new Error("Insert failed");
15913
15957
  if (!built)
15914
15958
  built = _sql.unsafe(sqlText, params);
15915
15959
  const result = await runWithHooks(built, "insert");
@@ -15918,6 +15962,8 @@ function createQueryBuilder(state) {
15918
15962
  return result;
15919
15963
  },
15920
15964
  returningAll() {
15965
+ if (noRows)
15966
+ return emptyReturningBuilder();
15921
15967
  const returningSql = `${sqlText} RETURNING *`;
15922
15968
  const q = _sql.unsafe(returningSql, params);
15923
15969
  const runFirst = async () => {
@@ -16024,8 +16070,22 @@ function createQueryBuilder(state) {
16024
16070
  built = _sql.unsafe(sqlText, params);
16025
16071
  return this;
16026
16072
  }
16027
- if (expr && typeof expr === "object" && !("raw" in expr)) {
16073
+ if (isRawExpression(expr)) {
16074
+ sqlText = `${sqlText} ${getWhereKeyword()} ${expr.raw}`;
16075
+ built = _sql.unsafe(sqlText, params);
16076
+ return this;
16077
+ }
16078
+ if (isBoundSqlExpression(expr)) {
16079
+ const rendered = renderBoundSqlExpression(expr, params.length + 1);
16080
+ sqlText = `${sqlText} ${getWhereKeyword()} ${rendered.text}`;
16081
+ params.push(...rendered.parameters);
16082
+ built = _sql.unsafe(sqlText, params);
16083
+ return this;
16084
+ }
16085
+ if (expr && typeof expr === "object") {
16028
16086
  const keys = Object.keys(expr);
16087
+ if (keys.length === 0)
16088
+ throw new TypeError("[query-builder] updateTable.where({}): an empty object is not a filter. Pass a condition, or drop the where() call if updating every row is intended.");
16029
16089
  const len = keys.length;
16030
16090
  const baseIdx = params.length;
16031
16091
  const conditions = Array.from({ length: len });
@@ -16035,8 +16095,9 @@ function createQueryBuilder(state) {
16035
16095
  }
16036
16096
  sqlText = `${sqlText} ${getWhereKeyword()} ${conditions.join(" AND ")}`;
16037
16097
  built = _sql.unsafe(sqlText, params);
16098
+ return this;
16038
16099
  }
16039
- return this;
16100
+ throw new TypeError(`[query-builder] updateTable.where(): expected a condition, got ${expr === null ? "null" : typeof expr}. Refusing to run an UPDATE with no WHERE \u2014 drop the where() call if updating every row is intended.`);
16040
16101
  },
16041
16102
  whereNull(column) {
16042
16103
  const keyword = SQL_PATTERNS.WHERE.test(sqlText) ? "AND" : "WHERE";
@@ -16051,22 +16112,40 @@ function createQueryBuilder(state) {
16051
16112
  return this;
16052
16113
  },
16053
16114
  returning(...cols) {
16054
- const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
16055
- const q = params.length > 0 ? _sql.unsafe(retText, params) : _sql.unsafe(retText);
16115
+ const parent = this;
16116
+ const retText = () => `${sqlText} RETURNING ${cols.join(", ")}`;
16117
+ const build = () => params.length > 0 ? _sql.unsafe(retText(), params) : _sql.unsafe(retText());
16056
16118
  const runFirst = async () => {
16057
- const rows = await runWithHooks(q, "update");
16119
+ const rows = await runWithHooks(build(), "update");
16058
16120
  return Array.isArray(rows) ? rows[0] : rows;
16059
16121
  };
16122
+ const unsupported = (name) => () => {
16123
+ throw new TypeError(`[query-builder] updateTable.returning(...).${name}() is not supported \u2014 an UPDATE cannot be ordered or limited through this builder. Apply the filter with where() instead.`);
16124
+ };
16060
16125
  const obj = {
16061
- where: () => obj,
16062
- andWhere: () => obj,
16063
- orWhere: () => obj,
16064
- orderBy: () => obj,
16065
- limit: () => obj,
16066
- offset: () => obj,
16067
- toSQL: () => makeExecutableQuery(q, retText),
16068
- execute: () => runWithHooks(q, "update"),
16069
- get: () => runWithHooks(q, "update"),
16126
+ where: (...args) => {
16127
+ parent.where(...args);
16128
+ return obj;
16129
+ },
16130
+ andWhere: (...args) => {
16131
+ parent.where(...args);
16132
+ return obj;
16133
+ },
16134
+ whereNull: (...args) => {
16135
+ parent.whereNull(...args);
16136
+ return obj;
16137
+ },
16138
+ whereNotNull: (...args) => {
16139
+ parent.whereNotNull(...args);
16140
+ return obj;
16141
+ },
16142
+ orWhere: unsupported("orWhere"),
16143
+ orderBy: unsupported("orderBy"),
16144
+ limit: unsupported("limit"),
16145
+ offset: unsupported("offset"),
16146
+ toSQL: () => makeExecutableQuery(build(), retText()),
16147
+ execute: () => runWithHooks(build(), "update"),
16148
+ get: () => runWithHooks(build(), "update"),
16070
16149
  first: runFirst,
16071
16150
  executeTakeFirst: runFirst,
16072
16151
  async firstOrFail() {
@@ -16105,17 +16184,17 @@ function createQueryBuilder(state) {
16105
16184
  return { numUpdatedRows: result };
16106
16185
  },
16107
16186
  returningAll() {
16108
- const retAllText = `${sqlText} RETURNING *`;
16109
- const q = params.length > 0 ? _sql.unsafe(retAllText, params) : _sql.unsafe(retAllText);
16187
+ const retAllText = () => `${sqlText} RETURNING *`;
16188
+ const build = () => params.length > 0 ? _sql.unsafe(retAllText(), params) : _sql.unsafe(retAllText());
16110
16189
  return {
16111
- toSQL: () => makeExecutableQuery(q, retAllText),
16112
- execute: () => runWithHooks(q, "update"),
16190
+ toSQL: () => makeExecutableQuery(build(), retAllText()),
16191
+ execute: () => runWithHooks(build(), "update"),
16113
16192
  async executeTakeFirst() {
16114
- const result = await runWithHooks(q, "update");
16193
+ const result = await runWithHooks(build(), "update");
16115
16194
  return Array.isArray(result) ? result[0] : result;
16116
16195
  },
16117
16196
  async executeTakeFirstOrThrow() {
16118
- const result = await runWithHooks(q, "update");
16197
+ const result = await runWithHooks(build(), "update");
16119
16198
  const first = Array.isArray(result) ? result[0] : result;
16120
16199
  if (!first)
16121
16200
  throw new Error("Update with RETURNING failed");
@@ -16180,8 +16259,22 @@ function createQueryBuilder(state) {
16180
16259
  built = null;
16181
16260
  return this;
16182
16261
  }
16183
- if (expr && typeof expr === "object" && !("raw" in expr)) {
16262
+ if (isRawExpression(expr)) {
16263
+ sqlText += ` ${getWhereKeyword()} ${expr.raw}`;
16264
+ built = null;
16265
+ return this;
16266
+ }
16267
+ if (isBoundSqlExpression(expr)) {
16268
+ const rendered = renderBoundSqlExpression(expr, delParams.length + 1);
16269
+ sqlText += ` ${getWhereKeyword()} ${rendered.text}`;
16270
+ delParams.push(...rendered.parameters);
16271
+ built = null;
16272
+ return this;
16273
+ }
16274
+ if (expr && typeof expr === "object") {
16184
16275
  const keys = Object.keys(expr);
16276
+ if (keys.length === 0)
16277
+ throw new TypeError("[query-builder] deleteFrom.where({}): an empty object is not a filter. Pass a condition, or drop the where() call if deleting every row is intended.");
16185
16278
  const conditions = [];
16186
16279
  for (const key of keys) {
16187
16280
  const paramIndex = delParams.length + 1;
@@ -16192,8 +16285,7 @@ function createQueryBuilder(state) {
16192
16285
  built = null;
16193
16286
  return this;
16194
16287
  }
16195
- built = applyWhere({}, ensureDelBuilt(), expr);
16196
- return this;
16288
+ throw new TypeError(`[query-builder] deleteFrom.where(): expected a condition, got ${expr === null ? "null" : typeof expr}. Refusing to run a DELETE with no WHERE \u2014 drop the where() call if deleting every row is intended.`);
16197
16289
  },
16198
16290
  whereNull(column) {
16199
16291
  sqlText += ` ${getWhereKeyword()} ${quoteId(String(column))} IS NULL`;
@@ -16206,22 +16298,48 @@ function createQueryBuilder(state) {
16206
16298
  return this;
16207
16299
  },
16208
16300
  returning(...cols) {
16209
- const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
16210
- const q = delParams.length > 0 ? _sql.unsafe(retText, delParams) : _sql.unsafe(retText);
16301
+ const parent = this;
16302
+ const retText = () => `${sqlText} RETURNING ${cols.join(", ")}`;
16303
+ const build = () => delParams.length > 0 ? _sql.unsafe(retText(), delParams) : _sql.unsafe(retText());
16304
+ const runDelete = async () => {
16305
+ await activeHooks()?.beforeDelete?.({ table: String(table), where: whereCondition });
16306
+ const rows = await runWithHooks(build(), "delete");
16307
+ try {
16308
+ await activeHooks()?.afterDelete?.({ table: String(table), where: whereCondition, result: rows });
16309
+ } catch {}
16310
+ return rows;
16311
+ };
16211
16312
  const runFirst = async () => {
16212
- const rows = await runWithHooks(q, "delete");
16313
+ const rows = await runDelete();
16213
16314
  return Array.isArray(rows) ? rows[0] : rows;
16214
16315
  };
16316
+ const unsupported = (name) => () => {
16317
+ throw new TypeError(`[query-builder] deleteFrom.returning(...).${name}() is not supported \u2014 a DELETE cannot be ordered or limited through this builder. Narrow it with where() instead.`);
16318
+ };
16215
16319
  const obj = {
16216
- where: () => obj,
16217
- andWhere: () => obj,
16218
- orWhere: () => obj,
16219
- orderBy: () => obj,
16220
- limit: () => obj,
16221
- offset: () => obj,
16222
- toSQL: () => makeExecutableQuery(q, retText),
16223
- execute: () => runWithHooks(q, "delete"),
16224
- get: () => runWithHooks(q, "delete"),
16320
+ where: (...args) => {
16321
+ parent.where(...args);
16322
+ return obj;
16323
+ },
16324
+ andWhere: (...args) => {
16325
+ parent.where(...args);
16326
+ return obj;
16327
+ },
16328
+ whereNull: (...args) => {
16329
+ parent.whereNull(...args);
16330
+ return obj;
16331
+ },
16332
+ whereNotNull: (...args) => {
16333
+ parent.whereNotNull(...args);
16334
+ return obj;
16335
+ },
16336
+ orWhere: unsupported("orWhere"),
16337
+ orderBy: unsupported("orderBy"),
16338
+ limit: unsupported("limit"),
16339
+ offset: unsupported("offset"),
16340
+ toSQL: () => makeExecutableQuery(build(), retText()),
16341
+ execute: () => runDelete(),
16342
+ get: () => runDelete(),
16225
16343
  first: runFirst,
16226
16344
  executeTakeFirst: runFirst,
16227
16345
  async firstOrFail() {
@@ -16265,17 +16383,17 @@ function createQueryBuilder(state) {
16265
16383
  return { numDeletedRows: result };
16266
16384
  },
16267
16385
  returningAll() {
16268
- const retAllText = `${sqlText} RETURNING *`;
16269
- const q = delParams.length > 0 ? _sql.unsafe(retAllText, delParams) : _sql.unsafe(retAllText);
16386
+ const retAllText = () => `${sqlText} RETURNING *`;
16387
+ const build = () => delParams.length > 0 ? _sql.unsafe(retAllText(), delParams) : _sql.unsafe(retAllText());
16270
16388
  return {
16271
- toSQL: () => makeExecutableQuery(q, retAllText),
16272
- execute: () => runWithHooks(q, "delete"),
16389
+ toSQL: () => makeExecutableQuery(build(), retAllText()),
16390
+ execute: () => runWithHooks(build(), "delete"),
16273
16391
  async executeTakeFirst() {
16274
- const result = await runWithHooks(q, "delete");
16392
+ const result = await runWithHooks(build(), "delete");
16275
16393
  return Array.isArray(result) ? result[0] : result;
16276
16394
  },
16277
16395
  async executeTakeFirstOrThrow() {
16278
- const result = await runWithHooks(q, "delete");
16396
+ const result = await runWithHooks(build(), "delete");
16279
16397
  const first = Array.isArray(result) ? result[0] : result;
16280
16398
  if (!first)
16281
16399
  throw new Error("Delete with RETURNING failed");
@@ -18939,11 +19057,66 @@ var init_vitess = __esm(() => {
18939
19057
  };
18940
19058
  });
18941
19059
 
18942
- // src/drivers/sqlite.ts
19060
+ // src/column-types.ts
18943
19061
  function isNumericPlanType(type) {
18944
19062
  return type === "integer" || type === "bigint" || type === "float" || type === "double" || type === "decimal";
18945
19063
  }
19064
+ function normalizeAttributeType(raw2) {
19065
+ if (typeof raw2 !== "string")
19066
+ return;
19067
+ switch (raw2.toLowerCase()) {
19068
+ case "string":
19069
+ return "string";
19070
+ case "text":
19071
+ return "text";
19072
+ case "integer":
19073
+ case "int":
19074
+ return "integer";
19075
+ case "number":
19076
+ return "integer";
19077
+ case "bigint":
19078
+ return "bigint";
19079
+ case "float":
19080
+ return "float";
19081
+ case "double":
19082
+ return "double";
19083
+ case "decimal":
19084
+ return "decimal";
19085
+ case "boolean":
19086
+ case "bool":
19087
+ return "boolean";
19088
+ case "date":
19089
+ return "date";
19090
+ case "datetime":
19091
+ case "timestamp":
19092
+ return "datetime";
19093
+ case "timestamptz":
19094
+ case "timestamp_tz":
19095
+ return "timestamptz";
19096
+ case "json":
19097
+ return "json";
19098
+ case "enum":
19099
+ return "enum";
19100
+ default:
19101
+ return;
19102
+ }
19103
+ }
19104
+ function sqliteAffinityFor(type) {
19105
+ switch (type) {
19106
+ case "boolean":
19107
+ case "integer":
19108
+ case "bigint":
19109
+ return "INTEGER";
19110
+ case "float":
19111
+ case "double":
19112
+ case "decimal":
19113
+ return "REAL";
19114
+ default:
19115
+ return "TEXT";
19116
+ }
19117
+ }
18946
19118
 
19119
+ // src/drivers/sqlite.ts
18947
19120
  class SQLiteDriver {
18948
19121
  quoteIdentifier(id) {
18949
19122
  return `"${id.replace(/"/g, '""')}"`;
@@ -18952,40 +19125,14 @@ class SQLiteDriver {
18952
19125
  if (column.name.endsWith("_id") && isNumericPlanType(column.type)) {
18953
19126
  return "INTEGER";
18954
19127
  }
18955
- switch (column.type) {
18956
- case "string":
18957
- return "TEXT";
18958
- case "text":
18959
- return "TEXT";
18960
- case "boolean":
18961
- return "INTEGER";
18962
- case "integer":
18963
- return "INTEGER";
18964
- case "bigint":
18965
- return "INTEGER";
18966
- case "float":
18967
- return "REAL";
18968
- case "double":
18969
- return "REAL";
18970
- case "decimal":
18971
- return "REAL";
18972
- case "date":
18973
- return "TEXT";
18974
- case "datetime":
18975
- return "TEXT";
18976
- case "timestamptz":
18977
- return "TEXT";
18978
- case "json":
18979
- return "TEXT";
18980
- case "enum":
18981
- if (column.enumValues && column.enumValues.length > 0) {
18982
- const enumValues = column.enumValues.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
18983
- return `TEXT CHECK (${this.quoteIdentifier(column.name)} IN (${enumValues}))`;
18984
- }
18985
- return "TEXT";
18986
- default:
18987
- return "TEXT";
19128
+ if (column.type === "enum") {
19129
+ if (column.enumValues && column.enumValues.length > 0) {
19130
+ const enumValues = column.enumValues.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
19131
+ return `TEXT CHECK (${this.quoteIdentifier(column.name)} IN (${enumValues}))`;
19132
+ }
19133
+ return "TEXT";
18988
19134
  }
19135
+ return sqliteAffinityFor(column.type);
18989
19136
  }
18990
19137
  getPrimaryKeyType(column) {
18991
19138
  return this.getColumnType(column);
@@ -25381,6 +25528,14 @@ function assertValidOrderByColumn(name, context) {
25381
25528
  if (parts.length > 2 || parts.some((p2) => p2.length === 0 || p2.length > 64 || !SAFE_SQL_IDENTIFIER.test(p2)))
25382
25529
  throw new TypeError(`[bun-query-builder] ${context}: invalid ORDER BY column '${name}' \u2014 expected 'column' or 'table.column' of [A-Za-z0-9_] \u2014 refusing to interpolate into SQL`);
25383
25530
  }
25531
+ function registerLocalModel(name, model) {
25532
+ localModels.set(name, model);
25533
+ relationCache.clear();
25534
+ }
25535
+ function clearLocalModels() {
25536
+ localModels.clear();
25537
+ relationCache.clear();
25538
+ }
25384
25539
  function getModelFromRegistry(name) {
25385
25540
  if (!_getModel) {
25386
25541
  try {
@@ -25391,7 +25546,7 @@ function getModelFromRegistry(name) {
25391
25546
  };
25392
25547
  }
25393
25548
  }
25394
- return _getModel(name);
25549
+ return _getModel(name) ?? localModels.get(name);
25395
25550
  }
25396
25551
  function toPostgresPlaceholders(sql2) {
25397
25552
  const hit = pgPlaceholderCache.get(sql2);
@@ -25589,6 +25744,7 @@ function findAttributeDef(attrs, key) {
25589
25744
  class ModelInstance {
25590
25745
  _attributes;
25591
25746
  _original;
25747
+ _forced = new Set;
25592
25748
  _definition;
25593
25749
  _hasSaved = false;
25594
25750
  _relations = {};
@@ -25722,7 +25878,10 @@ class ModelInstance {
25722
25878
  forceFill(data) {
25723
25879
  if (this._original === null)
25724
25880
  this._original = { ...this._attributes };
25725
- Object.assign(this._attributes, normalizeAttributeKeys(data));
25881
+ const normalized = normalizeAttributeKeys(data);
25882
+ Object.assign(this._attributes, normalized);
25883
+ for (const col of Object.keys(normalized))
25884
+ this._forced.add(col);
25726
25885
  return this;
25727
25886
  }
25728
25887
  async save() {
@@ -25754,7 +25913,7 @@ class ModelInstance {
25754
25913
  const attrs = this._definition.attributes;
25755
25914
  const data = {};
25756
25915
  for (const [key, attr] of Object.entries(attrs)) {
25757
- if (attr.guarded)
25916
+ if (attr.guarded && !this._forced.has(toSnakeCase(key)))
25758
25917
  continue;
25759
25918
  const col = toSnakeCase(key);
25760
25919
  if (this._attributes[col] !== undefined) {
@@ -27152,7 +27311,10 @@ function createModelInternal(definition) {
27152
27311
  });
27153
27312
  }
27154
27313
  function createModel(definition) {
27155
- return createModelInternal(definition);
27314
+ const model = createModelInternal(definition);
27315
+ if (definition?.name)
27316
+ registerLocalModel(definition.name, model);
27317
+ return model;
27156
27318
  }
27157
27319
  async function createTableFromModel(definition) {
27158
27320
  const exec = getExecutor();
@@ -27169,12 +27331,9 @@ async function createTableFromModel(definition) {
27169
27331
  if (emitted.has(name))
27170
27332
  continue;
27171
27333
  emitted.add(name);
27172
- let colType = "TEXT";
27173
- if (attr.type === "number")
27174
- colType = "INTEGER";
27175
- else if (attr.type === "boolean")
27176
- colType = "INTEGER";
27177
- if (name.endsWith("_id"))
27334
+ const declaredType = normalizeAttributeType(attr.type);
27335
+ let colType = sqliteAffinityFor(declaredType);
27336
+ if (name.endsWith("_id") && (declaredType === undefined || isNumericPlanType(declaredType)))
27178
27337
  colType = "INTEGER";
27179
27338
  let colDef = `${name} ${colType}`;
27180
27339
  if (attr.unique)
@@ -27240,12 +27399,13 @@ async function seedModel(definition, count, faker) {
27240
27399
  await exec.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
27241
27400
  }
27242
27401
  }
27243
- var SAFE_SQL_IDENTIFIER, _getModel = null, pgPlaceholderCache, PG_PLACEHOLDER_CACHE_MAX = 500, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", btmKeysCache, snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000, RESOLVABLE_RELATION_KINDS, DECLARABLE_RELATION_KINDS;
27402
+ var SAFE_SQL_IDENTIFIER, _getModel = null, localModels, pgPlaceholderCache, PG_PLACEHOLDER_CACHE_MAX = 500, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", btmKeysCache, snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000, RESOLVABLE_RELATION_KINDS, DECLARABLE_RELATION_KINDS;
27244
27403
  var init_orm = __esm(() => {
27245
27404
  init_config();
27246
27405
  init_db();
27247
27406
  init_sqlite_pragmas();
27248
27407
  SAFE_SQL_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/i;
27408
+ localModels = new Map;
27249
27409
  pgPlaceholderCache = new Map;
27250
27410
  btmKeysCache = new WeakMap;
27251
27411
  snakeCaseCache = new Map;
@@ -27290,6 +27450,7 @@ function hasModel(name) {
27290
27450
  }
27291
27451
  function clearModelRegistry() {
27292
27452
  modelRegistry.clear();
27453
+ clearLocalModels();
27293
27454
  }
27294
27455
  function registerModel(name, model) {
27295
27456
  modelRegistry.set(name, model);
@@ -29065,7 +29226,7 @@ function detectTypeFromValidationRule(rule) {
29065
29226
  }
29066
29227
  return;
29067
29228
  }
29068
- function normalizeAttributeType(value) {
29229
+ function normalizeAttributeType2(value) {
29069
29230
  if (typeof value !== "string")
29070
29231
  return;
29071
29232
  const type = value.trim().toLowerCase().replace(/[\s-]+/g, "_");
@@ -29155,7 +29316,7 @@ function buildMigrationPlan2(models, options) {
29155
29316
  let inferred;
29156
29317
  let enumValues;
29157
29318
  const isPk = attrName === primaryKey;
29158
- inferred = normalizeAttributeType(attr.type) ?? detectTypeFromValidationRule(attr.validation?.rule);
29319
+ inferred = normalizeAttributeType2(attr.type) ?? detectTypeFromValidationRule(attr.validation?.rule);
29159
29320
  if (inferred === "string") {
29160
29321
  const maxLength = extractMaxFromRules(attr.validation?.rule);
29161
29322
  if (maxLength && maxLength > MAX_BOUNDED_VARCHAR_LENGTH)
@@ -32354,7 +32515,7 @@ function getPrefix() {
32354
32515
  }
32355
32516
  var prefix = getPrefix();
32356
32517
  // package.json
32357
- var version2 = "0.2.41";
32518
+ var version2 = "0.2.44";
32358
32519
 
32359
32520
  // bin/cli.ts
32360
32521
  init_actions();
package/dist/client.d.ts CHANGED
@@ -2,7 +2,7 @@ import { config } from './config';
2
2
  import { resetConnection } from './db';
3
3
  import type { DatabaseSchema } from './schema';
4
4
  import type { DriverConnection } from './db';
5
- import type { QueryHooks } from './types';
5
+ import type { QueryHooks, SupportedDialect } from './types';
6
6
  import type { SchemaMeta } from './meta';
7
7
  /**
8
8
  * # `raw`
@@ -31,6 +31,24 @@ import type { SchemaMeta } from './meta';
31
31
  * ```
32
32
  */
33
33
  export declare function raw(strings: TemplateStringsArray | string, ...values: unknown[]): RawExpression;
34
+ /**
35
+ * Format a value for safe interpolation into a relationship-subquery
36
+ * fragment. Numbers / booleans / null pass through; everything else is
37
+ * rejected.
38
+ *
39
+ * **Strings are escaped for the configured dialect, not just for ANSI.**
40
+ * Doubling the single quote is correct on Postgres and SQLite and is not
41
+ * sufficient on the MySQL family, where a backslash is itself an escape
42
+ * character unless NO_BACKSLASH_ESCAPES is set. Doubling alone lets
43
+ * `x\'; DROP TABLE t; --` through: it becomes `'x\''; DROP TABLE t; --'`,
44
+ * MySQL reads `\'` as a literal quote inside the string, the next quote closes
45
+ * it, and the remainder executes as its own statement.
46
+ *
47
+ * So on a MySQL-like dialect the backslash is doubled first. Order matters:
48
+ * escaping quotes first and backslashes second would re-escape the backslash
49
+ * this function had just introduced.
50
+ */
51
+ export declare function escapeStringLiteral(value: string, dialect?: SupportedDialect): string;
34
52
  // eslint-disable-next-line pickier/no-unused-vars
35
53
  export declare function createQueryBuilder<DB extends DatabaseSchema<any>>(state?: Partial<InternalState>): QueryBuilder<DB>;
36
54
  /**