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/src/index.js CHANGED
@@ -475,7 +475,7 @@ class BrowserModelQueryBuilder {
475
475
  for (const where of this._wheres) {
476
476
  if (where.operator === "=") {
477
477
  params.append(where.column, String(where.value));
478
- } else if ((where.operator === "in" || where.operator === "not in") && Array.isArray(where.value)) {
478
+ } else if (where.operator === "in" && Array.isArray(where.value)) {
479
479
  params.append(`${where.column}[]`, where.value.join(","));
480
480
  } else if (where.operator === "is" && where.value === null) {
481
481
  params.append(`filter[${where.column}][is]`, "null");
@@ -12855,7 +12855,10 @@ function warnOnceBareSqlFragment(context) {
12855
12855
  if (warnedSqlFragmentContexts.has(context))
12856
12856
  return;
12857
12857
  warnedSqlFragmentContexts.add(context);
12858
- 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.`);
12858
+ 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.`);
12859
+ }
12860
+ function escapeStringLiteral(value, dialect = config5.dialect) {
12861
+ return isMysqlLike(dialect) ? value.replace(/\\/g, "\\\\").replace(/'/g, "''") : value.replace(/'/g, "''");
12859
12862
  }
12860
12863
  function formatSubqueryValue(val) {
12861
12864
  if (val === null)
@@ -12865,7 +12868,7 @@ function formatSubqueryValue(val) {
12865
12868
  if (typeof val === "boolean")
12866
12869
  return val ? "1" : "0";
12867
12870
  if (typeof val === "string")
12868
- return `'${val.replace(/'/g, "''")}'`;
12871
+ return `'${escapeStringLiteral(val)}'`;
12869
12872
  if (val instanceof Date)
12870
12873
  return `'${val.toISOString()}'`;
12871
12874
  throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
@@ -14486,11 +14489,13 @@ function createQueryBuilder(state) {
14486
14489
  const dialect = config5.dialect;
14487
14490
  const idx = whereParams.length + 1;
14488
14491
  if (dialect === "postgres") {
14492
+ const needsJsonbLift = typeof json === "number" || typeof json === "boolean";
14493
+ const operand = needsJsonbLift ? `to_jsonb(${getPlaceholder(idx)})` : getPlaceholder(idx);
14489
14494
  if (config5.sql?.jsonContainsMode === "function")
14490
- pushWhere("AND", `jsonb_contains(${column}, ${getPlaceholder(idx)})`);
14495
+ pushWhere("AND", `jsonb_contains(${column}, ${operand})`);
14491
14496
  else
14492
- pushWhere("AND", `${column} @> ${getPlaceholder(idx)}`);
14493
- whereParams.push(JSON.stringify(json));
14497
+ pushWhere("AND", `${column} @> ${operand}`);
14498
+ whereParams.push(json);
14494
14499
  } else if (isMysqlLike(dialect)) {
14495
14500
  pushWhere("AND", `JSON_CONTAINS(${column}, ${getPlaceholder(idx)})`);
14496
14501
  whereParams.push(JSON.stringify(json));
@@ -15787,6 +15792,31 @@ function createQueryBuilder(state) {
15787
15792
  let built;
15788
15793
  let sqlText = "";
15789
15794
  const params = [];
15795
+ let noRows = false;
15796
+ const emptyInsertResult = () => isPostgres ? [] : { changes: 0, lastInsertRowid: 0 };
15797
+ const emptyReturningBuilder = () => {
15798
+ const noRow = async () => {
15799
+ return;
15800
+ };
15801
+ const noRowOrFail = async () => {
15802
+ throw new Error("Insert with RETURNING returned no rows");
15803
+ };
15804
+ return {
15805
+ where: () => emptyReturningBuilder(),
15806
+ andWhere: () => emptyReturningBuilder(),
15807
+ orWhere: () => emptyReturningBuilder(),
15808
+ orderBy: () => emptyReturningBuilder(),
15809
+ limit: () => emptyReturningBuilder(),
15810
+ offset: () => emptyReturningBuilder(),
15811
+ toSQL: () => makeExecutableQuery(null, ""),
15812
+ execute: async () => [],
15813
+ get: async () => [],
15814
+ first: noRow,
15815
+ executeTakeFirst: noRow,
15816
+ firstOrFail: noRowOrFail,
15817
+ executeTakeFirstOrThrow: noRowOrFail
15818
+ };
15819
+ };
15790
15820
  const isPostgres = config5.dialect === "postgres";
15791
15821
  const quoteId = isPostgres ? (id) => `"${String(id).replace(/"/g, '""')}"` : isMysqlLike(config5.dialect) ? (id) => `\`${String(id).replace(/`/g, "``")}\`` : (id) => `"${String(id).replace(/"/g, '""')}"`;
15792
15822
  const getPlaceholder2 = isPostgres ? (index) => `$${index + 1}` : (_index) => "?";
@@ -15795,9 +15825,13 @@ function createQueryBuilder(state) {
15795
15825
  const rows = Array.isArray(data) ? data : [data];
15796
15826
  const rowCount = rows.length;
15797
15827
  if (rowCount === 0) {
15798
- built = _sql.unsafe("SELECT 1");
15828
+ noRows = true;
15829
+ built = null;
15830
+ sqlText = "";
15831
+ params.length = 0;
15799
15832
  return this;
15800
15833
  }
15834
+ noRows = false;
15801
15835
  const firstRow = rows[0];
15802
15836
  const keys = Object.keys(firstRow);
15803
15837
  const colCount = keys.length;
@@ -15851,6 +15885,8 @@ function createQueryBuilder(state) {
15851
15885
  return this;
15852
15886
  },
15853
15887
  returning(...cols) {
15888
+ if (noRows)
15889
+ return emptyReturningBuilder();
15854
15890
  const returningSql = `${sqlText} RETURNING ${cols.join(", ")}`;
15855
15891
  const q = _sql.unsafe(returningSql, params);
15856
15892
  const runFirst = async () => {
@@ -15884,11 +15920,15 @@ function createQueryBuilder(state) {
15884
15920
  };
15885
15921
  },
15886
15922
  toSQL() {
15923
+ if (noRows)
15924
+ return makeExecutableQuery(null, "");
15887
15925
  if (!built)
15888
15926
  built = _sql.unsafe(sqlText, params);
15889
15927
  return makeExecutableQuery(built, sqlText);
15890
15928
  },
15891
15929
  execute() {
15930
+ if (noRows)
15931
+ return Promise.resolve(emptyInsertResult());
15892
15932
  const hooks = activeHooks();
15893
15933
  const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hooks.beforeCreate || hooks.afterCreate || hasSlowQueryHook(hooks));
15894
15934
  if (!hasHooks) {
@@ -15903,12 +15943,16 @@ function createQueryBuilder(state) {
15903
15943
  return runWithHooks(built, "insert");
15904
15944
  },
15905
15945
  async executeTakeFirst() {
15946
+ if (noRows)
15947
+ return emptyInsertResult();
15906
15948
  if (!built)
15907
15949
  built = _sql.unsafe(sqlText, params);
15908
15950
  const result = await runWithHooks(built, "insert");
15909
15951
  return result;
15910
15952
  },
15911
15953
  async executeTakeFirstOrThrow() {
15954
+ if (noRows)
15955
+ throw new Error("Insert failed");
15912
15956
  if (!built)
15913
15957
  built = _sql.unsafe(sqlText, params);
15914
15958
  const result = await runWithHooks(built, "insert");
@@ -15917,6 +15961,8 @@ function createQueryBuilder(state) {
15917
15961
  return result;
15918
15962
  },
15919
15963
  returningAll() {
15964
+ if (noRows)
15965
+ return emptyReturningBuilder();
15920
15966
  const returningSql = `${sqlText} RETURNING *`;
15921
15967
  const q = _sql.unsafe(returningSql, params);
15922
15968
  const runFirst = async () => {
@@ -16023,8 +16069,22 @@ function createQueryBuilder(state) {
16023
16069
  built = _sql.unsafe(sqlText, params);
16024
16070
  return this;
16025
16071
  }
16026
- if (expr && typeof expr === "object" && !("raw" in expr)) {
16072
+ if (isRawExpression(expr)) {
16073
+ sqlText = `${sqlText} ${getWhereKeyword()} ${expr.raw}`;
16074
+ built = _sql.unsafe(sqlText, params);
16075
+ return this;
16076
+ }
16077
+ if (isBoundSqlExpression(expr)) {
16078
+ const rendered = renderBoundSqlExpression(expr, params.length + 1);
16079
+ sqlText = `${sqlText} ${getWhereKeyword()} ${rendered.text}`;
16080
+ params.push(...rendered.parameters);
16081
+ built = _sql.unsafe(sqlText, params);
16082
+ return this;
16083
+ }
16084
+ if (expr && typeof expr === "object") {
16027
16085
  const keys = Object.keys(expr);
16086
+ if (keys.length === 0)
16087
+ 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.");
16028
16088
  const len = keys.length;
16029
16089
  const baseIdx = params.length;
16030
16090
  const conditions = Array.from({ length: len });
@@ -16034,8 +16094,9 @@ function createQueryBuilder(state) {
16034
16094
  }
16035
16095
  sqlText = `${sqlText} ${getWhereKeyword()} ${conditions.join(" AND ")}`;
16036
16096
  built = _sql.unsafe(sqlText, params);
16097
+ return this;
16037
16098
  }
16038
- return this;
16099
+ 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.`);
16039
16100
  },
16040
16101
  whereNull(column) {
16041
16102
  const keyword = SQL_PATTERNS.WHERE.test(sqlText) ? "AND" : "WHERE";
@@ -16050,22 +16111,40 @@ function createQueryBuilder(state) {
16050
16111
  return this;
16051
16112
  },
16052
16113
  returning(...cols) {
16053
- const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
16054
- const q = params.length > 0 ? _sql.unsafe(retText, params) : _sql.unsafe(retText);
16114
+ const parent = this;
16115
+ const retText = () => `${sqlText} RETURNING ${cols.join(", ")}`;
16116
+ const build = () => params.length > 0 ? _sql.unsafe(retText(), params) : _sql.unsafe(retText());
16055
16117
  const runFirst = async () => {
16056
- const rows = await runWithHooks(q, "update");
16118
+ const rows = await runWithHooks(build(), "update");
16057
16119
  return Array.isArray(rows) ? rows[0] : rows;
16058
16120
  };
16121
+ const unsupported = (name) => () => {
16122
+ 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.`);
16123
+ };
16059
16124
  const obj = {
16060
- where: () => obj,
16061
- andWhere: () => obj,
16062
- orWhere: () => obj,
16063
- orderBy: () => obj,
16064
- limit: () => obj,
16065
- offset: () => obj,
16066
- toSQL: () => makeExecutableQuery(q, retText),
16067
- execute: () => runWithHooks(q, "update"),
16068
- get: () => runWithHooks(q, "update"),
16125
+ where: (...args) => {
16126
+ parent.where(...args);
16127
+ return obj;
16128
+ },
16129
+ andWhere: (...args) => {
16130
+ parent.where(...args);
16131
+ return obj;
16132
+ },
16133
+ whereNull: (...args) => {
16134
+ parent.whereNull(...args);
16135
+ return obj;
16136
+ },
16137
+ whereNotNull: (...args) => {
16138
+ parent.whereNotNull(...args);
16139
+ return obj;
16140
+ },
16141
+ orWhere: unsupported("orWhere"),
16142
+ orderBy: unsupported("orderBy"),
16143
+ limit: unsupported("limit"),
16144
+ offset: unsupported("offset"),
16145
+ toSQL: () => makeExecutableQuery(build(), retText()),
16146
+ execute: () => runWithHooks(build(), "update"),
16147
+ get: () => runWithHooks(build(), "update"),
16069
16148
  first: runFirst,
16070
16149
  executeTakeFirst: runFirst,
16071
16150
  async firstOrFail() {
@@ -16104,17 +16183,17 @@ function createQueryBuilder(state) {
16104
16183
  return { numUpdatedRows: result };
16105
16184
  },
16106
16185
  returningAll() {
16107
- const retAllText = `${sqlText} RETURNING *`;
16108
- const q = params.length > 0 ? _sql.unsafe(retAllText, params) : _sql.unsafe(retAllText);
16186
+ const retAllText = () => `${sqlText} RETURNING *`;
16187
+ const build = () => params.length > 0 ? _sql.unsafe(retAllText(), params) : _sql.unsafe(retAllText());
16109
16188
  return {
16110
- toSQL: () => makeExecutableQuery(q, retAllText),
16111
- execute: () => runWithHooks(q, "update"),
16189
+ toSQL: () => makeExecutableQuery(build(), retAllText()),
16190
+ execute: () => runWithHooks(build(), "update"),
16112
16191
  async executeTakeFirst() {
16113
- const result = await runWithHooks(q, "update");
16192
+ const result = await runWithHooks(build(), "update");
16114
16193
  return Array.isArray(result) ? result[0] : result;
16115
16194
  },
16116
16195
  async executeTakeFirstOrThrow() {
16117
- const result = await runWithHooks(q, "update");
16196
+ const result = await runWithHooks(build(), "update");
16118
16197
  const first = Array.isArray(result) ? result[0] : result;
16119
16198
  if (!first)
16120
16199
  throw new Error("Update with RETURNING failed");
@@ -16179,8 +16258,22 @@ function createQueryBuilder(state) {
16179
16258
  built = null;
16180
16259
  return this;
16181
16260
  }
16182
- if (expr && typeof expr === "object" && !("raw" in expr)) {
16261
+ if (isRawExpression(expr)) {
16262
+ sqlText += ` ${getWhereKeyword()} ${expr.raw}`;
16263
+ built = null;
16264
+ return this;
16265
+ }
16266
+ if (isBoundSqlExpression(expr)) {
16267
+ const rendered = renderBoundSqlExpression(expr, delParams.length + 1);
16268
+ sqlText += ` ${getWhereKeyword()} ${rendered.text}`;
16269
+ delParams.push(...rendered.parameters);
16270
+ built = null;
16271
+ return this;
16272
+ }
16273
+ if (expr && typeof expr === "object") {
16183
16274
  const keys = Object.keys(expr);
16275
+ if (keys.length === 0)
16276
+ 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.");
16184
16277
  const conditions = [];
16185
16278
  for (const key of keys) {
16186
16279
  const paramIndex = delParams.length + 1;
@@ -16191,8 +16284,7 @@ function createQueryBuilder(state) {
16191
16284
  built = null;
16192
16285
  return this;
16193
16286
  }
16194
- built = applyWhere({}, ensureDelBuilt(), expr);
16195
- return this;
16287
+ 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.`);
16196
16288
  },
16197
16289
  whereNull(column) {
16198
16290
  sqlText += ` ${getWhereKeyword()} ${quoteId(String(column))} IS NULL`;
@@ -16205,22 +16297,48 @@ function createQueryBuilder(state) {
16205
16297
  return this;
16206
16298
  },
16207
16299
  returning(...cols) {
16208
- const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
16209
- const q = delParams.length > 0 ? _sql.unsafe(retText, delParams) : _sql.unsafe(retText);
16300
+ const parent = this;
16301
+ const retText = () => `${sqlText} RETURNING ${cols.join(", ")}`;
16302
+ const build = () => delParams.length > 0 ? _sql.unsafe(retText(), delParams) : _sql.unsafe(retText());
16303
+ const runDelete = async () => {
16304
+ await activeHooks()?.beforeDelete?.({ table: String(table), where: whereCondition });
16305
+ const rows = await runWithHooks(build(), "delete");
16306
+ try {
16307
+ await activeHooks()?.afterDelete?.({ table: String(table), where: whereCondition, result: rows });
16308
+ } catch {}
16309
+ return rows;
16310
+ };
16210
16311
  const runFirst = async () => {
16211
- const rows = await runWithHooks(q, "delete");
16312
+ const rows = await runDelete();
16212
16313
  return Array.isArray(rows) ? rows[0] : rows;
16213
16314
  };
16315
+ const unsupported = (name) => () => {
16316
+ 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.`);
16317
+ };
16214
16318
  const obj = {
16215
- where: () => obj,
16216
- andWhere: () => obj,
16217
- orWhere: () => obj,
16218
- orderBy: () => obj,
16219
- limit: () => obj,
16220
- offset: () => obj,
16221
- toSQL: () => makeExecutableQuery(q, retText),
16222
- execute: () => runWithHooks(q, "delete"),
16223
- get: () => runWithHooks(q, "delete"),
16319
+ where: (...args) => {
16320
+ parent.where(...args);
16321
+ return obj;
16322
+ },
16323
+ andWhere: (...args) => {
16324
+ parent.where(...args);
16325
+ return obj;
16326
+ },
16327
+ whereNull: (...args) => {
16328
+ parent.whereNull(...args);
16329
+ return obj;
16330
+ },
16331
+ whereNotNull: (...args) => {
16332
+ parent.whereNotNull(...args);
16333
+ return obj;
16334
+ },
16335
+ orWhere: unsupported("orWhere"),
16336
+ orderBy: unsupported("orderBy"),
16337
+ limit: unsupported("limit"),
16338
+ offset: unsupported("offset"),
16339
+ toSQL: () => makeExecutableQuery(build(), retText()),
16340
+ execute: () => runDelete(),
16341
+ get: () => runDelete(),
16224
16342
  first: runFirst,
16225
16343
  executeTakeFirst: runFirst,
16226
16344
  async firstOrFail() {
@@ -16264,17 +16382,17 @@ function createQueryBuilder(state) {
16264
16382
  return { numDeletedRows: result };
16265
16383
  },
16266
16384
  returningAll() {
16267
- const retAllText = `${sqlText} RETURNING *`;
16268
- const q = delParams.length > 0 ? _sql.unsafe(retAllText, delParams) : _sql.unsafe(retAllText);
16385
+ const retAllText = () => `${sqlText} RETURNING *`;
16386
+ const build = () => delParams.length > 0 ? _sql.unsafe(retAllText(), delParams) : _sql.unsafe(retAllText());
16269
16387
  return {
16270
- toSQL: () => makeExecutableQuery(q, retAllText),
16271
- execute: () => runWithHooks(q, "delete"),
16388
+ toSQL: () => makeExecutableQuery(build(), retAllText()),
16389
+ execute: () => runWithHooks(build(), "delete"),
16272
16390
  async executeTakeFirst() {
16273
- const result = await runWithHooks(q, "delete");
16391
+ const result = await runWithHooks(build(), "delete");
16274
16392
  return Array.isArray(result) ? result[0] : result;
16275
16393
  },
16276
16394
  async executeTakeFirstOrThrow() {
16277
- const result = await runWithHooks(q, "delete");
16395
+ const result = await runWithHooks(build(), "delete");
16278
16396
  const first = Array.isArray(result) ? result[0] : result;
16279
16397
  if (!first)
16280
16398
  throw new Error("Delete with RETURNING failed");
@@ -18938,11 +19056,66 @@ var init_vitess = __esm(() => {
18938
19056
  };
18939
19057
  });
18940
19058
 
18941
- // src/drivers/sqlite.ts
19059
+ // src/column-types.ts
18942
19060
  function isNumericPlanType(type) {
18943
19061
  return type === "integer" || type === "bigint" || type === "float" || type === "double" || type === "decimal";
18944
19062
  }
19063
+ function normalizeAttributeType(raw2) {
19064
+ if (typeof raw2 !== "string")
19065
+ return;
19066
+ switch (raw2.toLowerCase()) {
19067
+ case "string":
19068
+ return "string";
19069
+ case "text":
19070
+ return "text";
19071
+ case "integer":
19072
+ case "int":
19073
+ return "integer";
19074
+ case "number":
19075
+ return "integer";
19076
+ case "bigint":
19077
+ return "bigint";
19078
+ case "float":
19079
+ return "float";
19080
+ case "double":
19081
+ return "double";
19082
+ case "decimal":
19083
+ return "decimal";
19084
+ case "boolean":
19085
+ case "bool":
19086
+ return "boolean";
19087
+ case "date":
19088
+ return "date";
19089
+ case "datetime":
19090
+ case "timestamp":
19091
+ return "datetime";
19092
+ case "timestamptz":
19093
+ case "timestamp_tz":
19094
+ return "timestamptz";
19095
+ case "json":
19096
+ return "json";
19097
+ case "enum":
19098
+ return "enum";
19099
+ default:
19100
+ return;
19101
+ }
19102
+ }
19103
+ function sqliteAffinityFor(type) {
19104
+ switch (type) {
19105
+ case "boolean":
19106
+ case "integer":
19107
+ case "bigint":
19108
+ return "INTEGER";
19109
+ case "float":
19110
+ case "double":
19111
+ case "decimal":
19112
+ return "REAL";
19113
+ default:
19114
+ return "TEXT";
19115
+ }
19116
+ }
18945
19117
 
19118
+ // src/drivers/sqlite.ts
18946
19119
  class SQLiteDriver {
18947
19120
  quoteIdentifier(id) {
18948
19121
  return `"${id.replace(/"/g, '""')}"`;
@@ -18951,40 +19124,14 @@ class SQLiteDriver {
18951
19124
  if (column.name.endsWith("_id") && isNumericPlanType(column.type)) {
18952
19125
  return "INTEGER";
18953
19126
  }
18954
- switch (column.type) {
18955
- case "string":
18956
- return "TEXT";
18957
- case "text":
18958
- return "TEXT";
18959
- case "boolean":
18960
- return "INTEGER";
18961
- case "integer":
18962
- return "INTEGER";
18963
- case "bigint":
18964
- return "INTEGER";
18965
- case "float":
18966
- return "REAL";
18967
- case "double":
18968
- return "REAL";
18969
- case "decimal":
18970
- return "REAL";
18971
- case "date":
18972
- return "TEXT";
18973
- case "datetime":
18974
- return "TEXT";
18975
- case "timestamptz":
18976
- return "TEXT";
18977
- case "json":
18978
- return "TEXT";
18979
- case "enum":
18980
- if (column.enumValues && column.enumValues.length > 0) {
18981
- const enumValues = column.enumValues.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
18982
- return `TEXT CHECK (${this.quoteIdentifier(column.name)} IN (${enumValues}))`;
18983
- }
18984
- return "TEXT";
18985
- default:
18986
- return "TEXT";
19127
+ if (column.type === "enum") {
19128
+ if (column.enumValues && column.enumValues.length > 0) {
19129
+ const enumValues = column.enumValues.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
19130
+ return `TEXT CHECK (${this.quoteIdentifier(column.name)} IN (${enumValues}))`;
19131
+ }
19132
+ return "TEXT";
18987
19133
  }
19134
+ return sqliteAffinityFor(column.type);
18988
19135
  }
18989
19136
  getPrimaryKeyType(column) {
18990
19137
  return this.getColumnType(column);
@@ -25380,6 +25527,14 @@ function assertValidOrderByColumn(name, context) {
25380
25527
  if (parts.length > 2 || parts.some((p2) => p2.length === 0 || p2.length > 64 || !SAFE_SQL_IDENTIFIER.test(p2)))
25381
25528
  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`);
25382
25529
  }
25530
+ function registerLocalModel(name, model) {
25531
+ localModels.set(name, model);
25532
+ relationCache.clear();
25533
+ }
25534
+ function clearLocalModels() {
25535
+ localModels.clear();
25536
+ relationCache.clear();
25537
+ }
25383
25538
  function getModelFromRegistry(name) {
25384
25539
  if (!_getModel) {
25385
25540
  try {
@@ -25390,7 +25545,7 @@ function getModelFromRegistry(name) {
25390
25545
  };
25391
25546
  }
25392
25547
  }
25393
- return _getModel(name);
25548
+ return _getModel(name) ?? localModels.get(name);
25394
25549
  }
25395
25550
  function toPostgresPlaceholders(sql2) {
25396
25551
  const hit = pgPlaceholderCache.get(sql2);
@@ -25588,6 +25743,7 @@ function findAttributeDef(attrs, key) {
25588
25743
  class ModelInstance {
25589
25744
  _attributes;
25590
25745
  _original;
25746
+ _forced = new Set;
25591
25747
  _definition;
25592
25748
  _hasSaved = false;
25593
25749
  _relations = {};
@@ -25721,7 +25877,10 @@ class ModelInstance {
25721
25877
  forceFill(data) {
25722
25878
  if (this._original === null)
25723
25879
  this._original = { ...this._attributes };
25724
- Object.assign(this._attributes, normalizeAttributeKeys(data));
25880
+ const normalized = normalizeAttributeKeys(data);
25881
+ Object.assign(this._attributes, normalized);
25882
+ for (const col of Object.keys(normalized))
25883
+ this._forced.add(col);
25725
25884
  return this;
25726
25885
  }
25727
25886
  async save() {
@@ -25753,7 +25912,7 @@ class ModelInstance {
25753
25912
  const attrs = this._definition.attributes;
25754
25913
  const data = {};
25755
25914
  for (const [key, attr] of Object.entries(attrs)) {
25756
- if (attr.guarded)
25915
+ if (attr.guarded && !this._forced.has(toSnakeCase(key)))
25757
25916
  continue;
25758
25917
  const col = toSnakeCase(key);
25759
25918
  if (this._attributes[col] !== undefined) {
@@ -27151,7 +27310,10 @@ function createModelInternal(definition) {
27151
27310
  });
27152
27311
  }
27153
27312
  function createModel(definition) {
27154
- return createModelInternal(definition);
27313
+ const model = createModelInternal(definition);
27314
+ if (definition?.name)
27315
+ registerLocalModel(definition.name, model);
27316
+ return model;
27155
27317
  }
27156
27318
  async function createTableFromModel(definition) {
27157
27319
  const exec = getExecutor();
@@ -27168,12 +27330,9 @@ async function createTableFromModel(definition) {
27168
27330
  if (emitted.has(name))
27169
27331
  continue;
27170
27332
  emitted.add(name);
27171
- let colType = "TEXT";
27172
- if (attr.type === "number")
27173
- colType = "INTEGER";
27174
- else if (attr.type === "boolean")
27175
- colType = "INTEGER";
27176
- if (name.endsWith("_id"))
27333
+ const declaredType = normalizeAttributeType(attr.type);
27334
+ let colType = sqliteAffinityFor(declaredType);
27335
+ if (name.endsWith("_id") && (declaredType === undefined || isNumericPlanType(declaredType)))
27177
27336
  colType = "INTEGER";
27178
27337
  let colDef = `${name} ${colType}`;
27179
27338
  if (attr.unique)
@@ -27239,12 +27398,13 @@ async function seedModel(definition, count, faker) {
27239
27398
  await exec.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
27240
27399
  }
27241
27400
  }
27242
- 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;
27401
+ 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;
27243
27402
  var init_orm = __esm(() => {
27244
27403
  init_config();
27245
27404
  init_db();
27246
27405
  init_sqlite_pragmas();
27247
27406
  SAFE_SQL_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/i;
27407
+ localModels = new Map;
27248
27408
  pgPlaceholderCache = new Map;
27249
27409
  btmKeysCache = new WeakMap;
27250
27410
  snakeCaseCache = new Map;
@@ -27289,6 +27449,7 @@ function hasModel(name) {
27289
27449
  }
27290
27450
  function clearModelRegistry() {
27291
27451
  modelRegistry.clear();
27452
+ clearLocalModels();
27292
27453
  }
27293
27454
  function registerModel(name, model) {
27294
27455
  modelRegistry.set(name, model);
@@ -29064,7 +29225,7 @@ function detectTypeFromValidationRule(rule) {
29064
29225
  }
29065
29226
  return;
29066
29227
  }
29067
- function normalizeAttributeType(value) {
29228
+ function normalizeAttributeType2(value) {
29068
29229
  if (typeof value !== "string")
29069
29230
  return;
29070
29231
  const type = value.trim().toLowerCase().replace(/[\s-]+/g, "_");
@@ -29154,7 +29315,7 @@ function buildMigrationPlan2(models, options) {
29154
29315
  let inferred;
29155
29316
  let enumValues;
29156
29317
  const isPk = attrName === primaryKey;
29157
- inferred = normalizeAttributeType(attr.type) ?? detectTypeFromValidationRule(attr.validation?.rule);
29318
+ inferred = normalizeAttributeType2(attr.type) ?? detectTypeFromValidationRule(attr.validation?.rule);
29158
29319
  if (inferred === "string") {
29159
29320
  const maxLength = extractMaxFromRules(attr.validation?.rule);
29160
29321
  if (maxLength && maxLength > MAX_BOUNDED_VARCHAR_LENGTH)
@@ -30380,6 +30541,7 @@ export {
30380
30541
  queryExplainAll as explainAllQueries,
30381
30542
  explain,
30382
30543
  executeMigration,
30544
+ escapeStringLiteral,
30383
30545
  dynamo,
30384
30546
  dumpDatabase,
30385
30547
  defineSeeder,
@@ -30414,6 +30576,7 @@ export {
30414
30576
  config5 as config,
30415
30577
  clearQueryCache,
30416
30578
  clearModelRegistry,
30579
+ clearLocalModels,
30417
30580
  checkSchema,
30418
30581
  canonicalizeDefault,
30419
30582
  canonicalStorageType,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-query-builder",
3
3
  "type": "module",
4
- "version": "0.2.41",
4
+ "version": "0.2.44",
5
5
  "description": "A simple yet performant query builder for TypeScript. Built with Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",