bun-query-builder 0.2.43 → 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");
@@ -15793,6 +15793,31 @@ function createQueryBuilder(state) {
15793
15793
  let built;
15794
15794
  let sqlText = "";
15795
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
+ };
15796
15821
  const isPostgres = config5.dialect === "postgres";
15797
15822
  const quoteId = isPostgres ? (id) => `"${String(id).replace(/"/g, '""')}"` : isMysqlLike(config5.dialect) ? (id) => `\`${String(id).replace(/`/g, "``")}\`` : (id) => `"${String(id).replace(/"/g, '""')}"`;
15798
15823
  const getPlaceholder2 = isPostgres ? (index) => `$${index + 1}` : (_index) => "?";
@@ -15801,9 +15826,13 @@ function createQueryBuilder(state) {
15801
15826
  const rows = Array.isArray(data) ? data : [data];
15802
15827
  const rowCount = rows.length;
15803
15828
  if (rowCount === 0) {
15804
- built = _sql.unsafe("SELECT 1");
15829
+ noRows = true;
15830
+ built = null;
15831
+ sqlText = "";
15832
+ params.length = 0;
15805
15833
  return this;
15806
15834
  }
15835
+ noRows = false;
15807
15836
  const firstRow = rows[0];
15808
15837
  const keys = Object.keys(firstRow);
15809
15838
  const colCount = keys.length;
@@ -15857,6 +15886,8 @@ function createQueryBuilder(state) {
15857
15886
  return this;
15858
15887
  },
15859
15888
  returning(...cols) {
15889
+ if (noRows)
15890
+ return emptyReturningBuilder();
15860
15891
  const returningSql = `${sqlText} RETURNING ${cols.join(", ")}`;
15861
15892
  const q = _sql.unsafe(returningSql, params);
15862
15893
  const runFirst = async () => {
@@ -15890,11 +15921,15 @@ function createQueryBuilder(state) {
15890
15921
  };
15891
15922
  },
15892
15923
  toSQL() {
15924
+ if (noRows)
15925
+ return makeExecutableQuery(null, "");
15893
15926
  if (!built)
15894
15927
  built = _sql.unsafe(sqlText, params);
15895
15928
  return makeExecutableQuery(built, sqlText);
15896
15929
  },
15897
15930
  execute() {
15931
+ if (noRows)
15932
+ return Promise.resolve(emptyInsertResult());
15898
15933
  const hooks = activeHooks();
15899
15934
  const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hooks.beforeCreate || hooks.afterCreate || hasSlowQueryHook(hooks));
15900
15935
  if (!hasHooks) {
@@ -15909,12 +15944,16 @@ function createQueryBuilder(state) {
15909
15944
  return runWithHooks(built, "insert");
15910
15945
  },
15911
15946
  async executeTakeFirst() {
15947
+ if (noRows)
15948
+ return emptyInsertResult();
15912
15949
  if (!built)
15913
15950
  built = _sql.unsafe(sqlText, params);
15914
15951
  const result = await runWithHooks(built, "insert");
15915
15952
  return result;
15916
15953
  },
15917
15954
  async executeTakeFirstOrThrow() {
15955
+ if (noRows)
15956
+ throw new Error("Insert failed");
15918
15957
  if (!built)
15919
15958
  built = _sql.unsafe(sqlText, params);
15920
15959
  const result = await runWithHooks(built, "insert");
@@ -15923,6 +15962,8 @@ function createQueryBuilder(state) {
15923
15962
  return result;
15924
15963
  },
15925
15964
  returningAll() {
15965
+ if (noRows)
15966
+ return emptyReturningBuilder();
15926
15967
  const returningSql = `${sqlText} RETURNING *`;
15927
15968
  const q = _sql.unsafe(returningSql, params);
15928
15969
  const runFirst = async () => {
@@ -16029,8 +16070,22 @@ function createQueryBuilder(state) {
16029
16070
  built = _sql.unsafe(sqlText, params);
16030
16071
  return this;
16031
16072
  }
16032
- 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") {
16033
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.");
16034
16089
  const len = keys.length;
16035
16090
  const baseIdx = params.length;
16036
16091
  const conditions = Array.from({ length: len });
@@ -16040,8 +16095,9 @@ function createQueryBuilder(state) {
16040
16095
  }
16041
16096
  sqlText = `${sqlText} ${getWhereKeyword()} ${conditions.join(" AND ")}`;
16042
16097
  built = _sql.unsafe(sqlText, params);
16098
+ return this;
16043
16099
  }
16044
- 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.`);
16045
16101
  },
16046
16102
  whereNull(column) {
16047
16103
  const keyword = SQL_PATTERNS.WHERE.test(sqlText) ? "AND" : "WHERE";
@@ -16056,22 +16112,40 @@ function createQueryBuilder(state) {
16056
16112
  return this;
16057
16113
  },
16058
16114
  returning(...cols) {
16059
- const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
16060
- 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());
16061
16118
  const runFirst = async () => {
16062
- const rows = await runWithHooks(q, "update");
16119
+ const rows = await runWithHooks(build(), "update");
16063
16120
  return Array.isArray(rows) ? rows[0] : rows;
16064
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
+ };
16065
16125
  const obj = {
16066
- where: () => obj,
16067
- andWhere: () => obj,
16068
- orWhere: () => obj,
16069
- orderBy: () => obj,
16070
- limit: () => obj,
16071
- offset: () => obj,
16072
- toSQL: () => makeExecutableQuery(q, retText),
16073
- execute: () => runWithHooks(q, "update"),
16074
- 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"),
16075
16149
  first: runFirst,
16076
16150
  executeTakeFirst: runFirst,
16077
16151
  async firstOrFail() {
@@ -16110,17 +16184,17 @@ function createQueryBuilder(state) {
16110
16184
  return { numUpdatedRows: result };
16111
16185
  },
16112
16186
  returningAll() {
16113
- const retAllText = `${sqlText} RETURNING *`;
16114
- 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());
16115
16189
  return {
16116
- toSQL: () => makeExecutableQuery(q, retAllText),
16117
- execute: () => runWithHooks(q, "update"),
16190
+ toSQL: () => makeExecutableQuery(build(), retAllText()),
16191
+ execute: () => runWithHooks(build(), "update"),
16118
16192
  async executeTakeFirst() {
16119
- const result = await runWithHooks(q, "update");
16193
+ const result = await runWithHooks(build(), "update");
16120
16194
  return Array.isArray(result) ? result[0] : result;
16121
16195
  },
16122
16196
  async executeTakeFirstOrThrow() {
16123
- const result = await runWithHooks(q, "update");
16197
+ const result = await runWithHooks(build(), "update");
16124
16198
  const first = Array.isArray(result) ? result[0] : result;
16125
16199
  if (!first)
16126
16200
  throw new Error("Update with RETURNING failed");
@@ -16185,8 +16259,22 @@ function createQueryBuilder(state) {
16185
16259
  built = null;
16186
16260
  return this;
16187
16261
  }
16188
- 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") {
16189
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.");
16190
16278
  const conditions = [];
16191
16279
  for (const key of keys) {
16192
16280
  const paramIndex = delParams.length + 1;
@@ -16197,8 +16285,7 @@ function createQueryBuilder(state) {
16197
16285
  built = null;
16198
16286
  return this;
16199
16287
  }
16200
- built = applyWhere({}, ensureDelBuilt(), expr);
16201
- 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.`);
16202
16289
  },
16203
16290
  whereNull(column) {
16204
16291
  sqlText += ` ${getWhereKeyword()} ${quoteId(String(column))} IS NULL`;
@@ -16211,22 +16298,48 @@ function createQueryBuilder(state) {
16211
16298
  return this;
16212
16299
  },
16213
16300
  returning(...cols) {
16214
- const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
16215
- 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
+ };
16216
16312
  const runFirst = async () => {
16217
- const rows = await runWithHooks(q, "delete");
16313
+ const rows = await runDelete();
16218
16314
  return Array.isArray(rows) ? rows[0] : rows;
16219
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
+ };
16220
16319
  const obj = {
16221
- where: () => obj,
16222
- andWhere: () => obj,
16223
- orWhere: () => obj,
16224
- orderBy: () => obj,
16225
- limit: () => obj,
16226
- offset: () => obj,
16227
- toSQL: () => makeExecutableQuery(q, retText),
16228
- execute: () => runWithHooks(q, "delete"),
16229
- 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(),
16230
16343
  first: runFirst,
16231
16344
  executeTakeFirst: runFirst,
16232
16345
  async firstOrFail() {
@@ -16270,17 +16383,17 @@ function createQueryBuilder(state) {
16270
16383
  return { numDeletedRows: result };
16271
16384
  },
16272
16385
  returningAll() {
16273
- const retAllText = `${sqlText} RETURNING *`;
16274
- 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());
16275
16388
  return {
16276
- toSQL: () => makeExecutableQuery(q, retAllText),
16277
- execute: () => runWithHooks(q, "delete"),
16389
+ toSQL: () => makeExecutableQuery(build(), retAllText()),
16390
+ execute: () => runWithHooks(build(), "delete"),
16278
16391
  async executeTakeFirst() {
16279
- const result = await runWithHooks(q, "delete");
16392
+ const result = await runWithHooks(build(), "delete");
16280
16393
  return Array.isArray(result) ? result[0] : result;
16281
16394
  },
16282
16395
  async executeTakeFirstOrThrow() {
16283
- const result = await runWithHooks(q, "delete");
16396
+ const result = await runWithHooks(build(), "delete");
16284
16397
  const first = Array.isArray(result) ? result[0] : result;
16285
16398
  if (!first)
16286
16399
  throw new Error("Delete with RETURNING failed");
@@ -18944,11 +19057,66 @@ var init_vitess = __esm(() => {
18944
19057
  };
18945
19058
  });
18946
19059
 
18947
- // src/drivers/sqlite.ts
19060
+ // src/column-types.ts
18948
19061
  function isNumericPlanType(type) {
18949
19062
  return type === "integer" || type === "bigint" || type === "float" || type === "double" || type === "decimal";
18950
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
+ }
18951
19118
 
19119
+ // src/drivers/sqlite.ts
18952
19120
  class SQLiteDriver {
18953
19121
  quoteIdentifier(id) {
18954
19122
  return `"${id.replace(/"/g, '""')}"`;
@@ -18957,40 +19125,14 @@ class SQLiteDriver {
18957
19125
  if (column.name.endsWith("_id") && isNumericPlanType(column.type)) {
18958
19126
  return "INTEGER";
18959
19127
  }
18960
- switch (column.type) {
18961
- case "string":
18962
- return "TEXT";
18963
- case "text":
18964
- return "TEXT";
18965
- case "boolean":
18966
- return "INTEGER";
18967
- case "integer":
18968
- return "INTEGER";
18969
- case "bigint":
18970
- return "INTEGER";
18971
- case "float":
18972
- return "REAL";
18973
- case "double":
18974
- return "REAL";
18975
- case "decimal":
18976
- return "REAL";
18977
- case "date":
18978
- return "TEXT";
18979
- case "datetime":
18980
- return "TEXT";
18981
- case "timestamptz":
18982
- return "TEXT";
18983
- case "json":
18984
- return "TEXT";
18985
- case "enum":
18986
- if (column.enumValues && column.enumValues.length > 0) {
18987
- const enumValues = column.enumValues.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
18988
- return `TEXT CHECK (${this.quoteIdentifier(column.name)} IN (${enumValues}))`;
18989
- }
18990
- return "TEXT";
18991
- default:
18992
- 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";
18993
19134
  }
19135
+ return sqliteAffinityFor(column.type);
18994
19136
  }
18995
19137
  getPrimaryKeyType(column) {
18996
19138
  return this.getColumnType(column);
@@ -25386,6 +25528,14 @@ function assertValidOrderByColumn(name, context) {
25386
25528
  if (parts.length > 2 || parts.some((p2) => p2.length === 0 || p2.length > 64 || !SAFE_SQL_IDENTIFIER.test(p2)))
25387
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`);
25388
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
+ }
25389
25539
  function getModelFromRegistry(name) {
25390
25540
  if (!_getModel) {
25391
25541
  try {
@@ -25396,7 +25546,7 @@ function getModelFromRegistry(name) {
25396
25546
  };
25397
25547
  }
25398
25548
  }
25399
- return _getModel(name);
25549
+ return _getModel(name) ?? localModels.get(name);
25400
25550
  }
25401
25551
  function toPostgresPlaceholders(sql2) {
25402
25552
  const hit = pgPlaceholderCache.get(sql2);
@@ -25594,6 +25744,7 @@ function findAttributeDef(attrs, key) {
25594
25744
  class ModelInstance {
25595
25745
  _attributes;
25596
25746
  _original;
25747
+ _forced = new Set;
25597
25748
  _definition;
25598
25749
  _hasSaved = false;
25599
25750
  _relations = {};
@@ -25727,7 +25878,10 @@ class ModelInstance {
25727
25878
  forceFill(data) {
25728
25879
  if (this._original === null)
25729
25880
  this._original = { ...this._attributes };
25730
- 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);
25731
25885
  return this;
25732
25886
  }
25733
25887
  async save() {
@@ -25759,7 +25913,7 @@ class ModelInstance {
25759
25913
  const attrs = this._definition.attributes;
25760
25914
  const data = {};
25761
25915
  for (const [key, attr] of Object.entries(attrs)) {
25762
- if (attr.guarded)
25916
+ if (attr.guarded && !this._forced.has(toSnakeCase(key)))
25763
25917
  continue;
25764
25918
  const col = toSnakeCase(key);
25765
25919
  if (this._attributes[col] !== undefined) {
@@ -27157,7 +27311,10 @@ function createModelInternal(definition) {
27157
27311
  });
27158
27312
  }
27159
27313
  function createModel(definition) {
27160
- return createModelInternal(definition);
27314
+ const model = createModelInternal(definition);
27315
+ if (definition?.name)
27316
+ registerLocalModel(definition.name, model);
27317
+ return model;
27161
27318
  }
27162
27319
  async function createTableFromModel(definition) {
27163
27320
  const exec = getExecutor();
@@ -27174,12 +27331,9 @@ async function createTableFromModel(definition) {
27174
27331
  if (emitted.has(name))
27175
27332
  continue;
27176
27333
  emitted.add(name);
27177
- let colType = "TEXT";
27178
- if (attr.type === "number")
27179
- colType = "INTEGER";
27180
- else if (attr.type === "boolean")
27181
- colType = "INTEGER";
27182
- if (name.endsWith("_id"))
27334
+ const declaredType = normalizeAttributeType(attr.type);
27335
+ let colType = sqliteAffinityFor(declaredType);
27336
+ if (name.endsWith("_id") && (declaredType === undefined || isNumericPlanType(declaredType)))
27183
27337
  colType = "INTEGER";
27184
27338
  let colDef = `${name} ${colType}`;
27185
27339
  if (attr.unique)
@@ -27245,12 +27399,13 @@ async function seedModel(definition, count, faker) {
27245
27399
  await exec.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
27246
27400
  }
27247
27401
  }
27248
- 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;
27249
27403
  var init_orm = __esm(() => {
27250
27404
  init_config();
27251
27405
  init_db();
27252
27406
  init_sqlite_pragmas();
27253
27407
  SAFE_SQL_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/i;
27408
+ localModels = new Map;
27254
27409
  pgPlaceholderCache = new Map;
27255
27410
  btmKeysCache = new WeakMap;
27256
27411
  snakeCaseCache = new Map;
@@ -27295,6 +27450,7 @@ function hasModel(name) {
27295
27450
  }
27296
27451
  function clearModelRegistry() {
27297
27452
  modelRegistry.clear();
27453
+ clearLocalModels();
27298
27454
  }
27299
27455
  function registerModel(name, model) {
27300
27456
  modelRegistry.set(name, model);
@@ -29070,7 +29226,7 @@ function detectTypeFromValidationRule(rule) {
29070
29226
  }
29071
29227
  return;
29072
29228
  }
29073
- function normalizeAttributeType(value) {
29229
+ function normalizeAttributeType2(value) {
29074
29230
  if (typeof value !== "string")
29075
29231
  return;
29076
29232
  const type = value.trim().toLowerCase().replace(/[\s-]+/g, "_");
@@ -29160,7 +29316,7 @@ function buildMigrationPlan2(models, options) {
29160
29316
  let inferred;
29161
29317
  let enumValues;
29162
29318
  const isPk = attrName === primaryKey;
29163
- inferred = normalizeAttributeType(attr.type) ?? detectTypeFromValidationRule(attr.validation?.rule);
29319
+ inferred = normalizeAttributeType2(attr.type) ?? detectTypeFromValidationRule(attr.validation?.rule);
29164
29320
  if (inferred === "string") {
29165
29321
  const maxLength = extractMaxFromRules(attr.validation?.rule);
29166
29322
  if (maxLength && maxLength > MAX_BOUNDED_VARCHAR_LENGTH)
@@ -32359,7 +32515,7 @@ function getPrefix() {
32359
32515
  }
32360
32516
  var prefix = getPrefix();
32361
32517
  // package.json
32362
- var version2 = "0.2.43";
32518
+ var version2 = "0.2.44";
32363
32519
 
32364
32520
  // bin/cli.ts
32365
32521
  init_actions();
@@ -0,0 +1,26 @@
1
+ import type { NormalizedColumnType } from './migrations';
2
+ /**
3
+ * Whether a canonical type stores a number.
4
+ *
5
+ * Used to decide when the `*_id` naming heuristic may coerce a column to
6
+ * INTEGER. Declared text types must never be coerced by a name heuristic —
7
+ * external ids are frequently strings (tickers, wallet addresses, hashes).
8
+ */
9
+ export declare function isNumericPlanType(type: string | undefined): boolean;
10
+ /**
11
+ * Map a declared attribute type onto the canonical set.
12
+ *
13
+ * Accepts the spellings the model layer and the validation-rule reader already
14
+ * accept, so `type: 'int'`, `'bool'` and `'timestamp'` land where their long
15
+ * forms do. Returns `undefined` for anything unrecognised, which callers treat
16
+ * as "no opinion" rather than guessing.
17
+ */
18
+ export declare function normalizeAttributeType(raw: unknown): NormalizedColumnType | undefined;
19
+ /**
20
+ * The SQLite storage class for a canonical type.
21
+ *
22
+ * `enum` is TEXT here; the migration driver additionally emits a CHECK
23
+ * constraint, which needs the column name and a quoter and so stays with the
24
+ * driver.
25
+ */
26
+ export declare function sqliteAffinityFor(type: NormalizedColumnType | undefined): 'TEXT' | 'INTEGER' | 'REAL';
@@ -1,11 +1,6 @@
1
+ import { isNumericPlanType } from '../column-types';
1
2
  import type { ColumnPlan, IndexPlan, RebuildTableSpec, TablePlan } from '../migrations';
2
3
  import type { DateBucketGrain } from './postgres';
3
- /**
4
- * Whether a plan type belongs to the numeric family. Used by the `_id`
5
- * safety net (numeric ids must store as INTEGER on SQLite) — non-numeric
6
- * declared types must never be coerced by the name heuristic.
7
- */
8
- export declare function isNumericPlanType(type: string | undefined): boolean;
9
4
  export declare interface DialectDriver {
10
5
  createEnumType: (enumTypeName: string, values: string[]) => string
11
6
  createTable: (table: TablePlan) => string
@@ -45,3 +40,13 @@ export declare class SQLiteDriver implements DialectDriver {
45
40
  dateBucket(column: string, grain: DateBucketGrain, offsetHours?: number): string;
46
41
  booleanLiteral(value: boolean): string;
47
42
  }
43
+ /**
44
+ * Whether a plan type belongs to the numeric family. Used by the `_id`
45
+ * safety net (numeric ids must store as INTEGER on SQLite) — non-numeric
46
+ * declared types must never be coerced by the name heuristic.
47
+ *
48
+ * Defined in column-types.ts now, so the ORM's create-table path applies the
49
+ * same rule from the same source. Re-exported because this is where callers
50
+ * already import it from.
51
+ */
52
+ export { isNumericPlanType };
package/dist/model.d.ts CHANGED
@@ -51,6 +51,10 @@ export declare function getModelRegistry(): Record<string, ReturnType<typeof cre
51
51
  export declare function hasModel(name: string): boolean;
52
52
  /**
53
53
  * Clear all registered models. Primarily useful for testing.
54
+ *
55
+ * Also drops the models `createModel` registered internally, and the memoized
56
+ * relation resolutions derived from them. Leaving either behind lets a stale
57
+ * resolution from one test file decide a relation's table in the next.
54
58
  */
55
59
  export declare function clearModelRegistry(): void;
56
60
  /**
package/dist/orm.d.ts CHANGED
@@ -14,6 +14,8 @@ export type {
14
14
  FillableKeys,
15
15
  HiddenKeys,
16
16
  };
17
+ /** Drops the `createModel` models. Paired with `clearModelRegistry()`. */
18
+ export declare function clearLocalModels(): void;
17
19
  /**
18
20
  * Extract an affected-row count from a Bun SQL driver result.
19
21
  *
@@ -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");
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");
@@ -15792,6 +15792,31 @@ function createQueryBuilder(state) {
15792
15792
  let built;
15793
15793
  let sqlText = "";
15794
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
+ };
15795
15820
  const isPostgres = config5.dialect === "postgres";
15796
15821
  const quoteId = isPostgres ? (id) => `"${String(id).replace(/"/g, '""')}"` : isMysqlLike(config5.dialect) ? (id) => `\`${String(id).replace(/`/g, "``")}\`` : (id) => `"${String(id).replace(/"/g, '""')}"`;
15797
15822
  const getPlaceholder2 = isPostgres ? (index) => `$${index + 1}` : (_index) => "?";
@@ -15800,9 +15825,13 @@ function createQueryBuilder(state) {
15800
15825
  const rows = Array.isArray(data) ? data : [data];
15801
15826
  const rowCount = rows.length;
15802
15827
  if (rowCount === 0) {
15803
- built = _sql.unsafe("SELECT 1");
15828
+ noRows = true;
15829
+ built = null;
15830
+ sqlText = "";
15831
+ params.length = 0;
15804
15832
  return this;
15805
15833
  }
15834
+ noRows = false;
15806
15835
  const firstRow = rows[0];
15807
15836
  const keys = Object.keys(firstRow);
15808
15837
  const colCount = keys.length;
@@ -15856,6 +15885,8 @@ function createQueryBuilder(state) {
15856
15885
  return this;
15857
15886
  },
15858
15887
  returning(...cols) {
15888
+ if (noRows)
15889
+ return emptyReturningBuilder();
15859
15890
  const returningSql = `${sqlText} RETURNING ${cols.join(", ")}`;
15860
15891
  const q = _sql.unsafe(returningSql, params);
15861
15892
  const runFirst = async () => {
@@ -15889,11 +15920,15 @@ function createQueryBuilder(state) {
15889
15920
  };
15890
15921
  },
15891
15922
  toSQL() {
15923
+ if (noRows)
15924
+ return makeExecutableQuery(null, "");
15892
15925
  if (!built)
15893
15926
  built = _sql.unsafe(sqlText, params);
15894
15927
  return makeExecutableQuery(built, sqlText);
15895
15928
  },
15896
15929
  execute() {
15930
+ if (noRows)
15931
+ return Promise.resolve(emptyInsertResult());
15897
15932
  const hooks = activeHooks();
15898
15933
  const hasHooks = hooks && (hooks.onQueryStart || hooks.onQueryEnd || hooks.onQueryError || hooks.startSpan || hooks.beforeCreate || hooks.afterCreate || hasSlowQueryHook(hooks));
15899
15934
  if (!hasHooks) {
@@ -15908,12 +15943,16 @@ function createQueryBuilder(state) {
15908
15943
  return runWithHooks(built, "insert");
15909
15944
  },
15910
15945
  async executeTakeFirst() {
15946
+ if (noRows)
15947
+ return emptyInsertResult();
15911
15948
  if (!built)
15912
15949
  built = _sql.unsafe(sqlText, params);
15913
15950
  const result = await runWithHooks(built, "insert");
15914
15951
  return result;
15915
15952
  },
15916
15953
  async executeTakeFirstOrThrow() {
15954
+ if (noRows)
15955
+ throw new Error("Insert failed");
15917
15956
  if (!built)
15918
15957
  built = _sql.unsafe(sqlText, params);
15919
15958
  const result = await runWithHooks(built, "insert");
@@ -15922,6 +15961,8 @@ function createQueryBuilder(state) {
15922
15961
  return result;
15923
15962
  },
15924
15963
  returningAll() {
15964
+ if (noRows)
15965
+ return emptyReturningBuilder();
15925
15966
  const returningSql = `${sqlText} RETURNING *`;
15926
15967
  const q = _sql.unsafe(returningSql, params);
15927
15968
  const runFirst = async () => {
@@ -16028,8 +16069,22 @@ function createQueryBuilder(state) {
16028
16069
  built = _sql.unsafe(sqlText, params);
16029
16070
  return this;
16030
16071
  }
16031
- 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") {
16032
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.");
16033
16088
  const len = keys.length;
16034
16089
  const baseIdx = params.length;
16035
16090
  const conditions = Array.from({ length: len });
@@ -16039,8 +16094,9 @@ function createQueryBuilder(state) {
16039
16094
  }
16040
16095
  sqlText = `${sqlText} ${getWhereKeyword()} ${conditions.join(" AND ")}`;
16041
16096
  built = _sql.unsafe(sqlText, params);
16097
+ return this;
16042
16098
  }
16043
- 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.`);
16044
16100
  },
16045
16101
  whereNull(column) {
16046
16102
  const keyword = SQL_PATTERNS.WHERE.test(sqlText) ? "AND" : "WHERE";
@@ -16055,22 +16111,40 @@ function createQueryBuilder(state) {
16055
16111
  return this;
16056
16112
  },
16057
16113
  returning(...cols) {
16058
- const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
16059
- 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());
16060
16117
  const runFirst = async () => {
16061
- const rows = await runWithHooks(q, "update");
16118
+ const rows = await runWithHooks(build(), "update");
16062
16119
  return Array.isArray(rows) ? rows[0] : rows;
16063
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
+ };
16064
16124
  const obj = {
16065
- where: () => obj,
16066
- andWhere: () => obj,
16067
- orWhere: () => obj,
16068
- orderBy: () => obj,
16069
- limit: () => obj,
16070
- offset: () => obj,
16071
- toSQL: () => makeExecutableQuery(q, retText),
16072
- execute: () => runWithHooks(q, "update"),
16073
- 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"),
16074
16148
  first: runFirst,
16075
16149
  executeTakeFirst: runFirst,
16076
16150
  async firstOrFail() {
@@ -16109,17 +16183,17 @@ function createQueryBuilder(state) {
16109
16183
  return { numUpdatedRows: result };
16110
16184
  },
16111
16185
  returningAll() {
16112
- const retAllText = `${sqlText} RETURNING *`;
16113
- 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());
16114
16188
  return {
16115
- toSQL: () => makeExecutableQuery(q, retAllText),
16116
- execute: () => runWithHooks(q, "update"),
16189
+ toSQL: () => makeExecutableQuery(build(), retAllText()),
16190
+ execute: () => runWithHooks(build(), "update"),
16117
16191
  async executeTakeFirst() {
16118
- const result = await runWithHooks(q, "update");
16192
+ const result = await runWithHooks(build(), "update");
16119
16193
  return Array.isArray(result) ? result[0] : result;
16120
16194
  },
16121
16195
  async executeTakeFirstOrThrow() {
16122
- const result = await runWithHooks(q, "update");
16196
+ const result = await runWithHooks(build(), "update");
16123
16197
  const first = Array.isArray(result) ? result[0] : result;
16124
16198
  if (!first)
16125
16199
  throw new Error("Update with RETURNING failed");
@@ -16184,8 +16258,22 @@ function createQueryBuilder(state) {
16184
16258
  built = null;
16185
16259
  return this;
16186
16260
  }
16187
- 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") {
16188
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.");
16189
16277
  const conditions = [];
16190
16278
  for (const key of keys) {
16191
16279
  const paramIndex = delParams.length + 1;
@@ -16196,8 +16284,7 @@ function createQueryBuilder(state) {
16196
16284
  built = null;
16197
16285
  return this;
16198
16286
  }
16199
- built = applyWhere({}, ensureDelBuilt(), expr);
16200
- 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.`);
16201
16288
  },
16202
16289
  whereNull(column) {
16203
16290
  sqlText += ` ${getWhereKeyword()} ${quoteId(String(column))} IS NULL`;
@@ -16210,22 +16297,48 @@ function createQueryBuilder(state) {
16210
16297
  return this;
16211
16298
  },
16212
16299
  returning(...cols) {
16213
- const retText = `${sqlText} RETURNING ${cols.join(", ")}`;
16214
- 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
+ };
16215
16311
  const runFirst = async () => {
16216
- const rows = await runWithHooks(q, "delete");
16312
+ const rows = await runDelete();
16217
16313
  return Array.isArray(rows) ? rows[0] : rows;
16218
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
+ };
16219
16318
  const obj = {
16220
- where: () => obj,
16221
- andWhere: () => obj,
16222
- orWhere: () => obj,
16223
- orderBy: () => obj,
16224
- limit: () => obj,
16225
- offset: () => obj,
16226
- toSQL: () => makeExecutableQuery(q, retText),
16227
- execute: () => runWithHooks(q, "delete"),
16228
- 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(),
16229
16342
  first: runFirst,
16230
16343
  executeTakeFirst: runFirst,
16231
16344
  async firstOrFail() {
@@ -16269,17 +16382,17 @@ function createQueryBuilder(state) {
16269
16382
  return { numDeletedRows: result };
16270
16383
  },
16271
16384
  returningAll() {
16272
- const retAllText = `${sqlText} RETURNING *`;
16273
- 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());
16274
16387
  return {
16275
- toSQL: () => makeExecutableQuery(q, retAllText),
16276
- execute: () => runWithHooks(q, "delete"),
16388
+ toSQL: () => makeExecutableQuery(build(), retAllText()),
16389
+ execute: () => runWithHooks(build(), "delete"),
16277
16390
  async executeTakeFirst() {
16278
- const result = await runWithHooks(q, "delete");
16391
+ const result = await runWithHooks(build(), "delete");
16279
16392
  return Array.isArray(result) ? result[0] : result;
16280
16393
  },
16281
16394
  async executeTakeFirstOrThrow() {
16282
- const result = await runWithHooks(q, "delete");
16395
+ const result = await runWithHooks(build(), "delete");
16283
16396
  const first = Array.isArray(result) ? result[0] : result;
16284
16397
  if (!first)
16285
16398
  throw new Error("Delete with RETURNING failed");
@@ -18943,11 +19056,66 @@ var init_vitess = __esm(() => {
18943
19056
  };
18944
19057
  });
18945
19058
 
18946
- // src/drivers/sqlite.ts
19059
+ // src/column-types.ts
18947
19060
  function isNumericPlanType(type) {
18948
19061
  return type === "integer" || type === "bigint" || type === "float" || type === "double" || type === "decimal";
18949
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
+ }
18950
19117
 
19118
+ // src/drivers/sqlite.ts
18951
19119
  class SQLiteDriver {
18952
19120
  quoteIdentifier(id) {
18953
19121
  return `"${id.replace(/"/g, '""')}"`;
@@ -18956,40 +19124,14 @@ class SQLiteDriver {
18956
19124
  if (column.name.endsWith("_id") && isNumericPlanType(column.type)) {
18957
19125
  return "INTEGER";
18958
19126
  }
18959
- switch (column.type) {
18960
- case "string":
18961
- return "TEXT";
18962
- case "text":
18963
- return "TEXT";
18964
- case "boolean":
18965
- return "INTEGER";
18966
- case "integer":
18967
- return "INTEGER";
18968
- case "bigint":
18969
- return "INTEGER";
18970
- case "float":
18971
- return "REAL";
18972
- case "double":
18973
- return "REAL";
18974
- case "decimal":
18975
- return "REAL";
18976
- case "date":
18977
- return "TEXT";
18978
- case "datetime":
18979
- return "TEXT";
18980
- case "timestamptz":
18981
- return "TEXT";
18982
- case "json":
18983
- return "TEXT";
18984
- case "enum":
18985
- if (column.enumValues && column.enumValues.length > 0) {
18986
- const enumValues = column.enumValues.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
18987
- return `TEXT CHECK (${this.quoteIdentifier(column.name)} IN (${enumValues}))`;
18988
- }
18989
- return "TEXT";
18990
- default:
18991
- 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";
18992
19133
  }
19134
+ return sqliteAffinityFor(column.type);
18993
19135
  }
18994
19136
  getPrimaryKeyType(column) {
18995
19137
  return this.getColumnType(column);
@@ -25385,6 +25527,14 @@ function assertValidOrderByColumn(name, context) {
25385
25527
  if (parts.length > 2 || parts.some((p2) => p2.length === 0 || p2.length > 64 || !SAFE_SQL_IDENTIFIER.test(p2)))
25386
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`);
25387
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
+ }
25388
25538
  function getModelFromRegistry(name) {
25389
25539
  if (!_getModel) {
25390
25540
  try {
@@ -25395,7 +25545,7 @@ function getModelFromRegistry(name) {
25395
25545
  };
25396
25546
  }
25397
25547
  }
25398
- return _getModel(name);
25548
+ return _getModel(name) ?? localModels.get(name);
25399
25549
  }
25400
25550
  function toPostgresPlaceholders(sql2) {
25401
25551
  const hit = pgPlaceholderCache.get(sql2);
@@ -25593,6 +25743,7 @@ function findAttributeDef(attrs, key) {
25593
25743
  class ModelInstance {
25594
25744
  _attributes;
25595
25745
  _original;
25746
+ _forced = new Set;
25596
25747
  _definition;
25597
25748
  _hasSaved = false;
25598
25749
  _relations = {};
@@ -25726,7 +25877,10 @@ class ModelInstance {
25726
25877
  forceFill(data) {
25727
25878
  if (this._original === null)
25728
25879
  this._original = { ...this._attributes };
25729
- 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);
25730
25884
  return this;
25731
25885
  }
25732
25886
  async save() {
@@ -25758,7 +25912,7 @@ class ModelInstance {
25758
25912
  const attrs = this._definition.attributes;
25759
25913
  const data = {};
25760
25914
  for (const [key, attr] of Object.entries(attrs)) {
25761
- if (attr.guarded)
25915
+ if (attr.guarded && !this._forced.has(toSnakeCase(key)))
25762
25916
  continue;
25763
25917
  const col = toSnakeCase(key);
25764
25918
  if (this._attributes[col] !== undefined) {
@@ -27156,7 +27310,10 @@ function createModelInternal(definition) {
27156
27310
  });
27157
27311
  }
27158
27312
  function createModel(definition) {
27159
- return createModelInternal(definition);
27313
+ const model = createModelInternal(definition);
27314
+ if (definition?.name)
27315
+ registerLocalModel(definition.name, model);
27316
+ return model;
27160
27317
  }
27161
27318
  async function createTableFromModel(definition) {
27162
27319
  const exec = getExecutor();
@@ -27173,12 +27330,9 @@ async function createTableFromModel(definition) {
27173
27330
  if (emitted.has(name))
27174
27331
  continue;
27175
27332
  emitted.add(name);
27176
- let colType = "TEXT";
27177
- if (attr.type === "number")
27178
- colType = "INTEGER";
27179
- else if (attr.type === "boolean")
27180
- colType = "INTEGER";
27181
- if (name.endsWith("_id"))
27333
+ const declaredType = normalizeAttributeType(attr.type);
27334
+ let colType = sqliteAffinityFor(declaredType);
27335
+ if (name.endsWith("_id") && (declaredType === undefined || isNumericPlanType(declaredType)))
27182
27336
  colType = "INTEGER";
27183
27337
  let colDef = `${name} ${colType}`;
27184
27338
  if (attr.unique)
@@ -27244,12 +27398,13 @@ async function seedModel(definition, count, faker) {
27244
27398
  await exec.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
27245
27399
  }
27246
27400
  }
27247
- 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;
27248
27402
  var init_orm = __esm(() => {
27249
27403
  init_config();
27250
27404
  init_db();
27251
27405
  init_sqlite_pragmas();
27252
27406
  SAFE_SQL_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/i;
27407
+ localModels = new Map;
27253
27408
  pgPlaceholderCache = new Map;
27254
27409
  btmKeysCache = new WeakMap;
27255
27410
  snakeCaseCache = new Map;
@@ -27294,6 +27449,7 @@ function hasModel(name) {
27294
27449
  }
27295
27450
  function clearModelRegistry() {
27296
27451
  modelRegistry.clear();
27452
+ clearLocalModels();
27297
27453
  }
27298
27454
  function registerModel(name, model) {
27299
27455
  modelRegistry.set(name, model);
@@ -29069,7 +29225,7 @@ function detectTypeFromValidationRule(rule) {
29069
29225
  }
29070
29226
  return;
29071
29227
  }
29072
- function normalizeAttributeType(value) {
29228
+ function normalizeAttributeType2(value) {
29073
29229
  if (typeof value !== "string")
29074
29230
  return;
29075
29231
  const type = value.trim().toLowerCase().replace(/[\s-]+/g, "_");
@@ -29159,7 +29315,7 @@ function buildMigrationPlan2(models, options) {
29159
29315
  let inferred;
29160
29316
  let enumValues;
29161
29317
  const isPk = attrName === primaryKey;
29162
- inferred = normalizeAttributeType(attr.type) ?? detectTypeFromValidationRule(attr.validation?.rule);
29318
+ inferred = normalizeAttributeType2(attr.type) ?? detectTypeFromValidationRule(attr.validation?.rule);
29163
29319
  if (inferred === "string") {
29164
29320
  const maxLength = extractMaxFromRules(attr.validation?.rule);
29165
29321
  if (maxLength && maxLength > MAX_BOUNDED_VARCHAR_LENGTH)
@@ -30420,6 +30576,7 @@ export {
30420
30576
  config5 as config,
30421
30577
  clearQueryCache,
30422
30578
  clearModelRegistry,
30579
+ clearLocalModels,
30423
30580
  checkSchema,
30424
30581
  canonicalizeDefault,
30425
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.43",
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",