bun-query-builder 0.1.59 → 0.1.61

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
@@ -11703,6 +11703,7 @@ var init_config = __esm(() => {
11703
11703
  init_dist();
11704
11704
  defaultConfig4 = {
11705
11705
  verbose: true,
11706
+ snapshotDir: ".qb",
11706
11707
  dialect: "postgres",
11707
11708
  database: {
11708
11709
  database: "test_db",
@@ -12061,6 +12062,13 @@ function createSQLiteSQL(filename) {
12061
12062
  sqlFunction._wrapper = wrapper;
12062
12063
  return sqlFunction;
12063
12064
  }
12065
+ function sqliteFileFromUrl(url) {
12066
+ const stripped = url.replace(/^(?:sqlite|sqlite3|file):/i, "");
12067
+ if (stripped === url)
12068
+ return url;
12069
+ const path = stripped.startsWith("///") ? stripped.slice(2) : stripped.startsWith("//") ? stripped.slice(2) : stripped;
12070
+ return path || ":memory:";
12071
+ }
12064
12072
  function createConnectionString(dialect, dbConfig) {
12065
12073
  if ((dialect === "postgres" || dialect === "mysql" || dialect === "singlestore") && process19.env.DB_CONNECTION === dialect) {
12066
12074
  const e = process19.env;
@@ -12074,7 +12082,7 @@ function createConnectionString(dialect, dbConfig) {
12074
12082
  return `${scheme}://${envUser}:${envPass}@${envHost}${envPort ? `:${envPort}` : ""}/${envDb}${ssl2}`;
12075
12083
  }
12076
12084
  if (dbConfig.url) {
12077
- return dbConfig.url;
12085
+ return dialect === "sqlite" ? sqliteFileFromUrl(dbConfig.url) : dbConfig.url;
12078
12086
  }
12079
12087
  const { database, username, password, host = "localhost", port, ssl } = dbConfig;
12080
12088
  const sslQ = ssl || process19.env.DB_SSL === "true" || process19.env.DB_SSL === "1" ? "?ssl=true" : "";
@@ -12350,9 +12358,37 @@ var init_benchmark = __esm(() => {
12350
12358
  init_src2();
12351
12359
  });
12352
12360
 
12361
+ // src/inflect.ts
12362
+ function stripTrailingS(name) {
12363
+ return name.endsWith("s") ? name.slice(0, -1) : name;
12364
+ }
12365
+ function inflectSingular(name) {
12366
+ if (name.endsWith("ies") && name.length > 3) {
12367
+ const stem = name.slice(0, -3);
12368
+ const prev = stem[stem.length - 1];
12369
+ if (prev && !"aeiou".includes(prev))
12370
+ return `${stem}y`;
12371
+ }
12372
+ if (name.endsWith("es") && name.length > 2) {
12373
+ const stem = name.slice(0, -2);
12374
+ if (stem.endsWith("s") || stem.endsWith("x") || stem.endsWith("ch") || stem.endsWith("sh"))
12375
+ return stem;
12376
+ }
12377
+ if (name.endsWith("s") && !name.endsWith("ss") && !name.endsWith("us") && !name.endsWith("is"))
12378
+ return name.slice(0, -1);
12379
+ return name;
12380
+ }
12381
+ function singularizerFor(strategy) {
12382
+ if (strategy === "none")
12383
+ return (name) => name;
12384
+ if (strategy === "inflect")
12385
+ return inflectSingular;
12386
+ return stripTrailingS;
12387
+ }
12388
+
12353
12389
  // src/pivot.ts
12354
12390
  function defaultSingularize(name) {
12355
- return name.endsWith("s") ? name.slice(0, -1) : name;
12391
+ return singularizerFor(config5.relations?.singularizeStrategy)(name);
12356
12392
  }
12357
12393
  function resolvePivot(meta, parentTable, relationKey, options = {}) {
12358
12394
  const singularize = options.singularize ?? defaultSingularize;
@@ -12429,6 +12465,9 @@ function* iterateAllPivots(meta, options = {}) {
12429
12465
  }
12430
12466
  }
12431
12467
  }
12468
+ var init_pivot = __esm(() => {
12469
+ init_config();
12470
+ });
12432
12471
 
12433
12472
  // src/client.ts
12434
12473
  function isRawExpression(expr) {
@@ -12609,7 +12648,13 @@ class QueryCache {
12609
12648
  this.cache.clear();
12610
12649
  }
12611
12650
  setMaxSize(size) {
12612
- this.maxSize = size;
12651
+ this.maxSize = Math.max(0, Math.floor(size));
12652
+ while (this.cache.size > this.maxSize) {
12653
+ const lruKey = this.cache.keys().next().value;
12654
+ if (lruKey === undefined)
12655
+ break;
12656
+ this.cache.delete(lruKey);
12657
+ }
12613
12658
  }
12614
12659
  }
12615
12660
  function isRetriableTxError(err) {
@@ -12714,8 +12759,7 @@ function computeBackoffMs(attempt, cfg) {
12714
12759
  const max = Math.max(base, cfg?.maxMs ?? 2000);
12715
12760
  let ms = Math.min(max, base * factor ** Math.max(0, attempt - 1));
12716
12761
  if (cfg?.jitter) {
12717
- const jitter = Math.random() * ms * 0.2;
12718
- ms = ms - jitter / 2;
12762
+ ms = Math.min(max, ms * (0.9 + Math.random() * 0.2));
12719
12763
  }
12720
12764
  return Math.floor(ms);
12721
12765
  }
@@ -12967,11 +13011,7 @@ function createQueryBuilder(state) {
12967
13011
  const pivotColumns = new Map;
12968
13012
  const pivotConfigRelations = new Set;
12969
13013
  let cacheTtl = 60000;
12970
- const singularize = (name) => {
12971
- if (config5.relations.singularizeStrategy === "none")
12972
- return name;
12973
- return name.endsWith("s") ? name.slice(0, -1) : name;
12974
- };
13014
+ const singularize = (name) => singularizerFor(config5.relations.singularizeStrategy)(name);
12975
13015
  const resolvePivotLocal = (relationKey) => {
12976
13016
  if (!meta)
12977
13017
  return null;
@@ -15447,6 +15487,7 @@ function createQueryBuilder(state) {
15447
15487
  const colCount = keys.length;
15448
15488
  const totalParams = rowCount * colCount;
15449
15489
  params.length = totalParams;
15490
+ const columnList = isPostgres ? keys.map((k) => quoteId(k)).join(",") : keys.join(",");
15450
15491
  if (rowCount === 1) {
15451
15492
  if (!isPostgres) {
15452
15493
  let cols = quoteId(keys[0]);
@@ -15459,7 +15500,6 @@ function createQueryBuilder(state) {
15459
15500
  }
15460
15501
  sqlText = `INSERT INTO ${quoteId(table)}(${cols})VALUES(${placeholders})`;
15461
15502
  } else {
15462
- const columnList = keys.map((k) => quoteId(k)).join(",");
15463
15503
  sqlText = `INSERT INTO ${quoteId(table)}(${columnList})VALUES(`;
15464
15504
  for (let c = 0;c < colCount; c++) {
15465
15505
  if (c > 0)
@@ -15470,8 +15510,8 @@ function createQueryBuilder(state) {
15470
15510
  sqlText += ")";
15471
15511
  }
15472
15512
  } else {
15473
- const columnList = keys.map((k) => quoteId(k)).join(",");
15474
- sqlText = `INSERT INTO ${quoteId(table)}(${columnList})VALUES`;
15513
+ const columnList2 = keys.map((k) => quoteId(k)).join(",");
15514
+ sqlText = `INSERT INTO ${quoteId(table)}(${columnList2})VALUES`;
15475
15515
  let pidx = 0;
15476
15516
  for (let r = 0;r < rowCount; r++) {
15477
15517
  const row = rows[r];
@@ -16027,11 +16067,13 @@ function createQueryBuilder(state) {
16027
16067
  }
16028
16068
  },
16029
16069
  async savepoint(fn) {
16070
+ if (state?.inTransaction !== true)
16071
+ throw new Error("savepoint() must be called inside a transaction");
16030
16072
  const s = _sql;
16031
16073
  if (!s || typeof s.savepoint !== "function")
16032
- throw new Error("savepoint() must be called inside a transaction");
16074
+ throw new Error("savepoint() is not supported by the active connection");
16033
16075
  return await s.savepoint(async (sp) => {
16034
- const qb = createQueryBuilder({ sql: sp, meta, schema });
16076
+ const qb = createQueryBuilder({ sql: sp, meta, schema, inTransaction: true });
16035
16077
  return await fn(qb);
16036
16078
  });
16037
16079
  },
@@ -16448,6 +16490,7 @@ var SQL_PATTERNS, SAFE_WHERE_OPERATORS, warnedSqlFragmentContexts, queryCache, r
16448
16490
  var init_client = __esm(() => {
16449
16491
  init_config();
16450
16492
  init_db();
16493
+ init_pivot();
16451
16494
  SQL_PATTERNS = {
16452
16495
  SELECT_STAR: /^SELECT\s+\*/i,
16453
16496
  SELECT: /^SELECT\s+/i,
@@ -17048,7 +17091,7 @@ var init_db_info = __esm(() => {
17048
17091
  // src/actions/db-optimize.ts
17049
17092
  import process22 from "process";
17050
17093
  async function dbOptimize(options = {}) {
17051
- const dialect = options.dialect || process22.env.DB_DIALECT || "postgres";
17094
+ const dialect = options.dialect || process22.env.DB_DIALECT || config5.dialect || "postgres";
17052
17095
  const aggressive = options.aggressive || false;
17053
17096
  if (options.verbose) {
17054
17097
  console.log(`Optimizing ${dialect} database${aggressive ? " (aggressive mode)" : ""}...`);
@@ -17143,7 +17186,7 @@ var init_db_optimize = __esm(() => {
17143
17186
  // src/actions/db-wipe.ts
17144
17187
  import process23 from "process";
17145
17188
  async function dbWipe(options = {}) {
17146
- const dialect = options.dialect || process23.env.DB_DIALECT || "postgres";
17189
+ const dialect = options.dialect || process23.env.DB_DIALECT || config5.dialect || "postgres";
17147
17190
  if (options.verbose) {
17148
17191
  console.log(`Wiping all tables from ${dialect} database...`);
17149
17192
  }
@@ -18555,9 +18598,11 @@ function info(message) {
18555
18598
  if (config5.verbose)
18556
18599
  console.log(message);
18557
18600
  }
18601
+ function getSnapshotDir(workspaceRoot) {
18602
+ return join9(workspaceRoot, config5.snapshotDir || ".qb");
18603
+ }
18558
18604
  function getSnapshotPath(workspaceRoot, dialect) {
18559
- const snapshotDir = join9(workspaceRoot, ".qb");
18560
- return join9(snapshotDir, `model-snapshot.${dialect}.json`);
18605
+ return join9(getSnapshotDir(workspaceRoot), `model-snapshot.${dialect}.json`);
18561
18606
  }
18562
18607
  function loadPlanSnapshot(workspaceRoot, dialect) {
18563
18608
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
@@ -18582,7 +18627,7 @@ function loadPlanSnapshot(workspaceRoot, dialect) {
18582
18627
  }
18583
18628
  function savePlanSnapshot(workspaceRoot, dialect, plan) {
18584
18629
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
18585
- const snapshotDir = join9(workspaceRoot, ".qb");
18630
+ const snapshotDir = getSnapshotDir(workspaceRoot);
18586
18631
  const hash = hashMigrationPlan(plan);
18587
18632
  if (existsSync16(snapshotPath)) {
18588
18633
  try {
@@ -18761,7 +18806,7 @@ async function resetDatabase(dir, opts = {}) {
18761
18806
  if (!dir) {
18762
18807
  dir = join9(process25.cwd(), "app/Models");
18763
18808
  }
18764
- const dialect = opts.dialect || "postgres";
18809
+ const dialect = opts.dialect || config5.dialect || "postgres";
18765
18810
  const driver = getDialectDriver(dialect);
18766
18811
  const workspaceRoot = getWorkspaceRoot();
18767
18812
  try {
@@ -18861,7 +18906,7 @@ async function deleteMigrationFiles(dir, workspaceRoot, opts = {}) {
18861
18906
  if (!workspaceRoot) {
18862
18907
  workspaceRoot = getWorkspaceRoot();
18863
18908
  }
18864
- const dialect = String(opts.dialect || "postgres");
18909
+ const dialect = String(opts.dialect || config5.dialect || "postgres");
18865
18910
  const snapshotPath = getSnapshotPath(workspaceRoot, dialect);
18866
18911
  if (existsSync16(snapshotPath)) {
18867
18912
  unlinkSync(snapshotPath);
@@ -24692,6 +24737,13 @@ function normalizeAttributeKeys(data) {
24692
24737
  }
24693
24738
  return result;
24694
24739
  }
24740
+ function normalizeAttributeKeysInPlace(data) {
24741
+ for (const key in data) {
24742
+ if (toSnakeCase(key) !== key)
24743
+ return normalizeAttributeKeys(data);
24744
+ }
24745
+ return data;
24746
+ }
24695
24747
  function findAttributeDef(attrs, key) {
24696
24748
  if (attrs[key])
24697
24749
  return attrs[key];
@@ -24713,7 +24765,7 @@ class ModelInstance {
24713
24765
  _relations = {};
24714
24766
  constructor(definition, attributes = {}) {
24715
24767
  this._definition = definition;
24716
- this._attributes = normalizeAttributeKeys({ ...attributes });
24768
+ this._attributes = normalizeAttributeKeysInPlace({ ...attributes });
24717
24769
  this._original = null;
24718
24770
  const btmKeys = collectBelongsToManyKeys(definition);
24719
24771
  if (btmKeys.size === 0)
@@ -25014,6 +25066,9 @@ class ModelInstance {
25014
25066
  return json;
25015
25067
  }
25016
25068
  }
25069
+ function sqlColumn(name) {
25070
+ return toSnakeCase(name);
25071
+ }
25017
25072
  function toSnakeCase(str) {
25018
25073
  let cached = snakeCaseCache.get(str);
25019
25074
  if (cached !== undefined)
@@ -25157,12 +25212,12 @@ function resolveRelation(definition, relationName) {
25157
25212
  }
25158
25213
  pivotTable = throughModel.getTable?.() || throughModel.getDefinition?.()?.table || toTableName(config6.through);
25159
25214
  } else {
25160
- const a2 = parentTable.endsWith("s") ? parentTable.slice(0, -1) : parentTable;
25161
- const b2 = relatedTable.endsWith("s") ? relatedTable.slice(0, -1) : relatedTable;
25215
+ const a2 = singularizeWord(parentTable);
25216
+ const b2 = singularizeWord(relatedTable);
25162
25217
  pivotTable = [a2, b2].sort().join("_");
25163
25218
  }
25164
- const pivotFkParent = config6?.foreignKey || `${parentTable.endsWith("s") ? parentTable.slice(0, -1) : parentTable}_id`;
25165
- const pivotFkRelated = config6?.relatedKey || `${relatedTable.endsWith("s") ? relatedTable.slice(0, -1) : relatedTable}_id`;
25219
+ const pivotFkParent = config6?.foreignKey || `${singularizeWord(parentTable)}_id`;
25220
+ const pivotFkRelated = config6?.relatedKey || `${singularizeWord(relatedTable)}_id`;
25166
25221
  const pivotColumns = [];
25167
25222
  if (config6?.pivot?.columns) {
25168
25223
  for (const k2 of Object.keys(config6.pivot.columns))
@@ -25230,8 +25285,17 @@ function resolveRelation(definition, relationName) {
25230
25285
  return hot;
25231
25286
  return null;
25232
25287
  }
25288
+ function distinctParentIds(instances, pk) {
25289
+ const seen = new Set;
25290
+ for (const i2 of instances) {
25291
+ const v2 = i2.get(pk);
25292
+ if (v2 != null)
25293
+ seen.add(v2);
25294
+ }
25295
+ return [...seen];
25296
+ }
25233
25297
  function singularizeWord(table) {
25234
- return table.endsWith("s") ? table.slice(0, -1) : table;
25298
+ return singularizerFor(config5.relations?.singularizeStrategy)(table);
25235
25299
  }
25236
25300
 
25237
25301
  class BelongsToManyRelationBuilder {
@@ -25634,7 +25698,7 @@ class ModelQueryBuilder {
25634
25698
  }
25635
25699
  whereNotBetween(column, range) {
25636
25700
  assertValidIdentifier(column, "whereNotBetween(column)");
25637
- const col = column;
25701
+ const col = sqlColumn(column);
25638
25702
  this._wheres.push({
25639
25703
  raw: `(${col} < ? OR ${col} > ?)`,
25640
25704
  rawParams: [range[0], range[1]],
@@ -25699,13 +25763,14 @@ class ModelQueryBuilder {
25699
25763
  if (w.rawParams && w.rawParams.length > 0)
25700
25764
  params.push(...w.rawParams);
25701
25765
  } else if (w.value === null) {
25702
- clause = w.operator === "=" ? `${w.column} IS NULL` : `${w.column} IS NOT NULL`;
25766
+ const col = sqlColumn(w.column);
25767
+ clause = w.operator === "=" ? `${col} IS NULL` : `${col} IS NOT NULL`;
25703
25768
  } else if (w.operator === "in" || w.operator === "not in") {
25704
25769
  const arr = w.value;
25705
- clause = `${w.column} ${w.operator.toUpperCase()} (${arr.map(() => "?").join(", ")})`;
25770
+ clause = `${sqlColumn(w.column)} ${w.operator.toUpperCase()} (${arr.map(() => "?").join(", ")})`;
25706
25771
  params.push(...arr);
25707
25772
  } else {
25708
- clause = `${w.column} ${w.operator} ?`;
25773
+ clause = `${sqlColumn(w.column)} ${w.operator} ?`;
25709
25774
  params.push(w.value);
25710
25775
  }
25711
25776
  clauses.push(i2 === 0 ? clause : `${w.boolean.toUpperCase()} ${clause}`);
@@ -25726,13 +25791,14 @@ class ModelQueryBuilder {
25726
25791
  }
25727
25792
  buildQuery() {
25728
25793
  const params = [];
25729
- let sql2 = `SELECT ${this._select.join(", ")} FROM ${this._definition.table}`;
25794
+ const cols = this._select.map((c2) => c2 === "*" ? c2 : sqlColumn(c2));
25795
+ let sql2 = `SELECT ${cols.join(", ")} FROM ${this._definition.table}`;
25730
25796
  const whereBody = this.composeWhere(params);
25731
25797
  if (whereBody) {
25732
25798
  sql2 += ` WHERE ${whereBody}`;
25733
25799
  }
25734
25800
  if (this._orderBy.length > 0) {
25735
- sql2 += ` ORDER BY ${this._orderBy.map((o2) => `${o2.column} ${o2.direction.toUpperCase()}`).join(", ")}`;
25801
+ sql2 += ` ORDER BY ${this._orderBy.map((o2) => `${sqlColumn(o2.column)} ${o2.direction.toUpperCase()}`).join(", ")}`;
25736
25802
  }
25737
25803
  if (this._limit !== undefined)
25738
25804
  sql2 += ` LIMIT ${this._limit}`;
@@ -25746,165 +25812,168 @@ class ModelQueryBuilder {
25746
25812
  async eagerLoadRelations(instances) {
25747
25813
  if (instances.length === 0 || this._withRelations.length === 0)
25748
25814
  return;
25815
+ await Promise.all(this._withRelations.map((r2) => this.eagerLoadRelation(r2, instances)));
25816
+ }
25817
+ async eagerLoadRelation(relationName, instances) {
25749
25818
  const exec = getExecutor();
25750
25819
  const pk = this._definition.primaryKey || "id";
25751
- for (const relationName of this._withRelations) {
25752
- const cacheKey = `${this._definition.name}:${relationName}`;
25753
- let rel = relationCache.get(cacheKey);
25754
- if (rel === undefined) {
25755
- rel = resolveRelation(this._definition, relationName);
25756
- if (relationCache.size >= RELATION_CACHE_MAX)
25757
- relationCache.clear();
25758
- relationCache.set(cacheKey, rel);
25759
- }
25760
- if (!rel)
25761
- continue;
25762
- if (rel.type === "hasMany" || rel.type === "hasOne") {
25763
- const parentIds = instances.map((i2) => i2.get(pk)).filter((id) => id != null);
25764
- if (parentIds.length === 0)
25765
- continue;
25766
- const placeholders = parentIds.map(() => "?").join(", ");
25767
- const rows = await exec.all(`SELECT * FROM ${rel.relatedTable} WHERE ${rel.foreignKey} IN (${placeholders})`, parentIds);
25768
- const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
25769
- const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
25770
- if (rel.type === "hasMany") {
25771
- const grouped = new Map;
25772
- for (const row of rows) {
25773
- const fkVal = row[rel.foreignKey];
25774
- if (!grouped.has(fkVal))
25775
- grouped.set(fkVal, []);
25776
- grouped.get(fkVal).push(row);
25777
- }
25778
- for (const instance of instances) {
25779
- const related = grouped.get(instance.get(pk)) || [];
25780
- instance.setRelation(relationName, related.map((r2) => new ModelInstance(relDef, r2)));
25781
- }
25782
- } else {
25783
- const byFk = new Map;
25784
- for (const row of rows) {
25785
- byFk.set(row[rel.foreignKey], row);
25786
- }
25787
- for (const instance of instances) {
25788
- const row = byFk.get(instance.get(pk));
25789
- instance.setRelation(relationName, row ? new ModelInstance(relDef, row) : null);
25790
- }
25820
+ const cacheKey = `${this._definition.name}:${relationName}`;
25821
+ let rel = relationCache.get(cacheKey);
25822
+ if (rel === undefined) {
25823
+ rel = resolveRelation(this._definition, relationName);
25824
+ if (relationCache.size >= RELATION_CACHE_MAX)
25825
+ relationCache.clear();
25826
+ relationCache.set(cacheKey, rel);
25827
+ }
25828
+ if (!rel)
25829
+ return;
25830
+ if (rel.type === "hasMany" || rel.type === "hasOne") {
25831
+ const parentIds = distinctParentIds(instances, pk);
25832
+ if (parentIds.length === 0)
25833
+ return;
25834
+ const placeholders = parentIds.map(() => "?").join(", ");
25835
+ const rows = await exec.all(`SELECT * FROM ${rel.relatedTable} WHERE ${rel.foreignKey} IN (${placeholders})`, parentIds);
25836
+ const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
25837
+ const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
25838
+ if (rel.type === "hasMany") {
25839
+ const grouped = new Map;
25840
+ for (const row of rows) {
25841
+ const fkVal = row[rel.foreignKey];
25842
+ if (!grouped.has(fkVal))
25843
+ grouped.set(fkVal, []);
25844
+ grouped.get(fkVal).push(row);
25791
25845
  }
25792
- }
25793
- if (rel.type === "belongsTo") {
25794
- const fkSet = new Set;
25795
- for (const i2 of instances) {
25796
- const v2 = i2._attributes[rel.foreignKey];
25797
- if (v2 != null)
25798
- fkSet.add(v2);
25846
+ for (const instance of instances) {
25847
+ const related = grouped.get(instance.get(pk)) || [];
25848
+ instance.setRelation(relationName, related.map((r2) => new ModelInstance(relDef, r2)));
25799
25849
  }
25800
- const uniqueFkValues = [...fkSet];
25801
- if (uniqueFkValues.length === 0)
25802
- continue;
25803
- const placeholders = uniqueFkValues.map(() => "?").join(", ");
25804
- const rows = await exec.all(`SELECT * FROM ${rel.relatedTable} WHERE ${rel.localKey} IN (${placeholders})`, uniqueFkValues);
25805
- const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
25806
- const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
25807
- const byPk = new Map;
25850
+ } else {
25851
+ const byFk = new Map;
25808
25852
  for (const row of rows) {
25809
- byPk.set(row[rel.localKey], row);
25853
+ const k2 = row[rel.foreignKey];
25854
+ if (!byFk.has(k2))
25855
+ byFk.set(k2, row);
25810
25856
  }
25811
25857
  for (const instance of instances) {
25812
- const fkVal = instance._attributes[rel.foreignKey];
25813
- const row = byPk.get(fkVal);
25858
+ const row = byFk.get(instance.get(pk));
25814
25859
  instance.setRelation(relationName, row ? new ModelInstance(relDef, row) : null);
25815
25860
  }
25816
25861
  }
25817
- if (rel.type === "belongsToMany") {
25818
- const parentIds = instances.map((i2) => i2.get(pk)).filter((id) => id != null);
25819
- if (parentIds.length === 0)
25820
- continue;
25821
- if (!rel.pivotTable || !rel.pivotFkParent || !rel.pivotFkRelated)
25822
- continue;
25823
- const pivotPlaceholders = parentIds.map(() => "?").join(", ");
25824
- const pivotRows = await exec.all(`SELECT * FROM ${rel.pivotTable} WHERE ${rel.pivotFkParent} IN (${pivotPlaceholders})`, parentIds);
25825
- if (pivotRows.length === 0) {
25826
- for (const instance of instances)
25827
- instance.setRelation(relationName, []);
25828
- continue;
25829
- }
25830
- const relatedIdSet = new Set;
25831
- for (const p2 of pivotRows) {
25832
- const v2 = p2[rel.pivotFkRelated];
25833
- if (v2 != null)
25834
- relatedIdSet.add(v2);
25835
- }
25836
- const relatedIds = [...relatedIdSet];
25837
- const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
25838
- const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
25839
- const relatedPk = relDef?.primaryKey || "id";
25840
- let relatedRows = [];
25841
- if (relatedIds.length > 0) {
25842
- const relPlaceholders = relatedIds.map(() => "?").join(", ");
25843
- relatedRows = await exec.all(`SELECT * FROM ${rel.relatedTable} WHERE ${relatedPk} IN (${relPlaceholders})`, relatedIds);
25844
- }
25845
- const relatedByPk = new Map;
25846
- for (const r2 of relatedRows)
25847
- relatedByPk.set(r2[relatedPk], r2);
25848
- const pivotByParent = new Map;
25849
- for (const p2 of pivotRows) {
25850
- const key = p2[rel.pivotFkParent];
25851
- if (!pivotByParent.has(key))
25852
- pivotByParent.set(key, []);
25853
- pivotByParent.get(key).push(p2);
25854
- }
25855
- const pivotKnownKeys = new Set([rel.pivotFkParent, rel.pivotFkRelated]);
25856
- for (const instance of instances) {
25857
- const parentVal = instance.get(pk);
25858
- const myPivots = pivotByParent.get(parentVal) || [];
25859
- const relatedInstances = myPivots.map((p2) => {
25860
- const relRow = relatedByPk.get(p2[rel.pivotFkRelated]);
25861
- if (!relRow)
25862
- return null;
25863
- const inst = new ModelInstance(relDef, relRow);
25864
- const extras = {};
25865
- for (const [k2, v2] of Object.entries(p2)) {
25866
- if (!pivotKnownKeys.has(k2))
25867
- extras[k2] = v2;
25868
- }
25869
- inst.pivot = extras;
25870
- return inst;
25871
- }).filter((x2) => x2 !== null);
25872
- instance.setRelation(relationName, relatedInstances);
25873
- }
25862
+ }
25863
+ if (rel.type === "belongsTo") {
25864
+ const fkSet = new Set;
25865
+ for (const i2 of instances) {
25866
+ const v2 = i2._attributes[rel.foreignKey];
25867
+ if (v2 != null)
25868
+ fkSet.add(v2);
25874
25869
  }
25875
- if (rel.type === "hasManyThrough" || rel.type === "hasOneThrough") {
25876
- const parentIds = instances.map((i2) => i2.get(pk)).filter((id) => id != null);
25877
- if (parentIds.length === 0)
25878
- continue;
25879
- const throughPk = rel.throughLocalKey || "id";
25880
- const throughPh = parentIds.map(() => "?").join(", ");
25881
- const throughRows = await exec.all(`SELECT ${throughPk}, ${rel.throughForeignKey} FROM ${rel.throughTable} WHERE ${rel.throughForeignKey} IN (${throughPh})`, parentIds);
25882
- if (throughRows.length === 0) {
25883
- for (const instance of instances)
25884
- instance.setRelation(relationName, rel.type === "hasManyThrough" ? [] : null);
25870
+ const uniqueFkValues = [...fkSet];
25871
+ if (uniqueFkValues.length === 0)
25872
+ return;
25873
+ const placeholders = uniqueFkValues.map(() => "?").join(", ");
25874
+ const rows = await exec.all(`SELECT * FROM ${rel.relatedTable} WHERE ${rel.localKey} IN (${placeholders})`, uniqueFkValues);
25875
+ const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
25876
+ const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
25877
+ const byPk = new Map;
25878
+ for (const row of rows) {
25879
+ byPk.set(row[rel.localKey], row);
25880
+ }
25881
+ for (const instance of instances) {
25882
+ const fkVal = instance._attributes[rel.foreignKey];
25883
+ const row = byPk.get(fkVal);
25884
+ instance.setRelation(relationName, row ? new ModelInstance(relDef, row) : null);
25885
+ }
25886
+ }
25887
+ if (rel.type === "belongsToMany") {
25888
+ const parentIds = distinctParentIds(instances, pk);
25889
+ if (parentIds.length === 0)
25890
+ return;
25891
+ if (!rel.pivotTable || !rel.pivotFkParent || !rel.pivotFkRelated)
25892
+ return;
25893
+ const pivotPlaceholders = parentIds.map(() => "?").join(", ");
25894
+ const pivotRows = await exec.all(`SELECT * FROM ${rel.pivotTable} WHERE ${rel.pivotFkParent} IN (${pivotPlaceholders})`, parentIds);
25895
+ if (pivotRows.length === 0) {
25896
+ for (const instance of instances)
25897
+ instance.setRelation(relationName, []);
25898
+ return;
25899
+ }
25900
+ const relatedIdSet = new Set;
25901
+ for (const p2 of pivotRows) {
25902
+ const v2 = p2[rel.pivotFkRelated];
25903
+ if (v2 != null)
25904
+ relatedIdSet.add(v2);
25905
+ }
25906
+ const relatedIds = [...relatedIdSet];
25907
+ const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
25908
+ const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
25909
+ const relatedPk = relDef?.primaryKey || "id";
25910
+ let relatedRows = [];
25911
+ if (relatedIds.length > 0) {
25912
+ const relPlaceholders = relatedIds.map(() => "?").join(", ");
25913
+ relatedRows = await exec.all(`SELECT * FROM ${rel.relatedTable} WHERE ${relatedPk} IN (${relPlaceholders})`, relatedIds);
25914
+ }
25915
+ const relatedByPk = new Map;
25916
+ for (const r2 of relatedRows)
25917
+ relatedByPk.set(r2[relatedPk], r2);
25918
+ const pivotByParent = new Map;
25919
+ for (const p2 of pivotRows) {
25920
+ const key = p2[rel.pivotFkParent];
25921
+ if (!pivotByParent.has(key))
25922
+ pivotByParent.set(key, []);
25923
+ pivotByParent.get(key).push(p2);
25924
+ }
25925
+ const pivotKnownKeys = new Set([rel.pivotFkParent, rel.pivotFkRelated]);
25926
+ for (const instance of instances) {
25927
+ const parentVal = instance.get(pk);
25928
+ const myPivots = pivotByParent.get(parentVal) || [];
25929
+ const relatedInstances = myPivots.map((p2) => {
25930
+ const relRow = relatedByPk.get(p2[rel.pivotFkRelated]);
25931
+ if (!relRow)
25932
+ return null;
25933
+ const inst = new ModelInstance(relDef, relRow);
25934
+ const extras = {};
25935
+ for (const [k2, v2] of Object.entries(p2)) {
25936
+ if (!pivotKnownKeys.has(k2))
25937
+ extras[k2] = v2;
25938
+ }
25939
+ inst.pivot = extras;
25940
+ return inst;
25941
+ }).filter((x2) => x2 !== null);
25942
+ instance.setRelation(relationName, relatedInstances);
25943
+ }
25944
+ }
25945
+ if (rel.type === "hasManyThrough" || rel.type === "hasOneThrough") {
25946
+ const parentIds = distinctParentIds(instances, pk);
25947
+ if (parentIds.length === 0)
25948
+ return;
25949
+ const throughPk = rel.throughLocalKey || "id";
25950
+ const throughPh = parentIds.map(() => "?").join(", ");
25951
+ const throughRows = await exec.all(`SELECT ${throughPk}, ${rel.throughForeignKey} FROM ${rel.throughTable} WHERE ${rel.throughForeignKey} IN (${throughPh})`, parentIds);
25952
+ if (throughRows.length === 0) {
25953
+ for (const instance of instances)
25954
+ instance.setRelation(relationName, rel.type === "hasManyThrough" ? [] : null);
25955
+ return;
25956
+ }
25957
+ const throughToParent = new Map;
25958
+ for (const t2 of throughRows)
25959
+ throughToParent.set(t2[throughPk], t2[rel.throughForeignKey]);
25960
+ const throughIds = [...new Set(throughRows.map((t2) => t2[throughPk]))];
25961
+ const targetPh = throughIds.map(() => "?").join(", ");
25962
+ const targetRows = await exec.all(`SELECT * FROM ${rel.relatedTable} WHERE ${rel.targetForeignKey} IN (${targetPh})`, throughIds);
25963
+ const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
25964
+ const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
25965
+ const byParent = new Map;
25966
+ for (const row of targetRows) {
25967
+ const parentVal = throughToParent.get(row[rel.targetForeignKey]);
25968
+ if (parentVal == null)
25885
25969
  continue;
25886
- }
25887
- const throughToParent = new Map;
25888
- for (const t2 of throughRows)
25889
- throughToParent.set(t2[throughPk], t2[rel.throughForeignKey]);
25890
- const throughIds = [...new Set(throughRows.map((t2) => t2[throughPk]))];
25891
- const targetPh = throughIds.map(() => "?").join(", ");
25892
- const targetRows = await exec.all(`SELECT * FROM ${rel.relatedTable} WHERE ${rel.targetForeignKey} IN (${targetPh})`, throughIds);
25893
- const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
25894
- const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
25895
- const byParent = new Map;
25896
- for (const row of targetRows) {
25897
- const parentVal = throughToParent.get(row[rel.targetForeignKey]);
25898
- if (parentVal == null)
25899
- continue;
25900
- if (!byParent.has(parentVal))
25901
- byParent.set(parentVal, []);
25902
- byParent.get(parentVal).push(new ModelInstance(relDef, row));
25903
- }
25904
- for (const instance of instances) {
25905
- const group = byParent.get(instance.get(pk)) || [];
25906
- instance.setRelation(relationName, rel.type === "hasManyThrough" ? group : group[0] ?? null);
25907
- }
25970
+ if (!byParent.has(parentVal))
25971
+ byParent.set(parentVal, []);
25972
+ byParent.get(parentVal).push(new ModelInstance(relDef, row));
25973
+ }
25974
+ for (const instance of instances) {
25975
+ const group = byParent.get(instance.get(pk)) || [];
25976
+ instance.setRelation(relationName, rel.type === "hasManyThrough" ? group : group[0] ?? null);
25908
25977
  }
25909
25978
  }
25910
25979
  }
@@ -25964,7 +26033,8 @@ class ModelQueryBuilder {
25964
26033
  assertValidIdentifier(column, "increment(column)");
25965
26034
  const exec = getExecutor();
25966
26035
  const params = [amount];
25967
- let sql2 = `UPDATE ${this._definition.table} SET ${column} = ${column} + ?`;
26036
+ const incCol = sqlColumn(column);
26037
+ let sql2 = `UPDATE ${this._definition.table} SET ${incCol} = ${incCol} + ?`;
25968
26038
  if (timestampsEnabled(this._definition)) {
25969
26039
  sql2 += `, updated_at = ?`;
25970
26040
  params.push(formatNow());
@@ -26022,26 +26092,27 @@ class ModelQueryBuilder {
26022
26092
  assertValidIdentifier(column, "pluck(column)");
26023
26093
  const exec = getExecutor();
26024
26094
  const params = [];
26025
- let sql2 = `SELECT ${column} FROM ${this._definition.table}`;
26095
+ const pluckCol = sqlColumn(column);
26096
+ let sql2 = `SELECT ${pluckCol} FROM ${this._definition.table}`;
26026
26097
  const whereBody = this.composeWhere(params);
26027
26098
  if (whereBody) {
26028
26099
  sql2 += ` WHERE ${whereBody}`;
26029
26100
  }
26030
26101
  if (this._orderBy.length > 0) {
26031
- sql2 += ` ORDER BY ${this._orderBy.map((o2) => `${o2.column} ${o2.direction.toUpperCase()}`).join(", ")}`;
26102
+ sql2 += ` ORDER BY ${this._orderBy.map((o2) => `${sqlColumn(o2.column)} ${o2.direction.toUpperCase()}`).join(", ")}`;
26032
26103
  }
26033
26104
  if (this._limit !== undefined)
26034
26105
  sql2 += ` LIMIT ${this._limit}`;
26035
26106
  if (this._offset !== undefined)
26036
26107
  sql2 += ` OFFSET ${this._offset}`;
26037
26108
  const rows = await exec.all(sql2, params);
26038
- return rows.map((r2) => r2[column]);
26109
+ return rows.map((r2) => r2[pluckCol]);
26039
26110
  }
26040
26111
  async aggregate(fn, column) {
26041
26112
  assertValidIdentifier(column, `${fn}(column)`);
26042
26113
  const exec = getExecutor();
26043
26114
  const params = [];
26044
- let sql2 = `SELECT ${fn}(${column}) as v FROM ${this._definition.table}`;
26115
+ let sql2 = `SELECT ${fn}(${sqlColumn(column)}) as v FROM ${this._definition.table}`;
26045
26116
  const whereBody = this.composeWhere(params);
26046
26117
  if (whereBody) {
26047
26118
  sql2 += ` WHERE ${whereBody}`;
@@ -26080,7 +26151,7 @@ class ModelQueryBuilder {
26080
26151
  async update(data) {
26081
26152
  const exec = getExecutor();
26082
26153
  const entries = Object.entries(data);
26083
- const sets = entries.map(([k2]) => `${k2} = ?`).join(", ");
26154
+ const sets = entries.map(([k2]) => `${sqlColumn(k2)} = ?`).join(", ");
26084
26155
  const params = entries.map(([, v2]) => v2);
26085
26156
  if (timestampsEnabled(this._definition)) {
26086
26157
  params.push(formatNow());
@@ -27929,6 +28000,9 @@ function info2(message) {
27929
28000
  function snakeCase(str) {
27930
28001
  return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").replace(/(\d)([A-Z])/gi, "$1_$2").toLowerCase();
27931
28002
  }
28003
+ function singularizeTable(table) {
28004
+ return singularizerFor(config5.relations?.singularizeStrategy)(table);
28005
+ }
27932
28006
  function findWorkspaceRoot6(startPath) {
27933
28007
  let currentPath = startPath;
27934
28008
  while (currentPath !== dirname12(currentPath)) {
@@ -28317,7 +28391,7 @@ function buildMigrationPlan2(models, options) {
28317
28391
  const model = models[modelName];
28318
28392
  const traits = model?.traits ?? {};
28319
28393
  const modelTable = model.table || `${String(model.name).toLowerCase()}s`;
28320
- const singular = modelTable.replace(/s$/, "");
28394
+ const singular = singularizeTable(modelTable);
28321
28395
  if (traits.likeable) {
28322
28396
  const opts = typeof traits.likeable === "object" ? traits.likeable : {};
28323
28397
  const likeTable = opts.table || `${modelTable}_likes`;
@@ -28384,7 +28458,7 @@ function buildMigrationPlan2(models, options) {
28384
28458
  if (!btm || typeof btm !== "object" || Array.isArray(btm))
28385
28459
  continue;
28386
28460
  const parentTable = model.table || `${String(model.name).toLowerCase()}s`;
28387
- const parentSingular = parentTable.replace(/s$/, "");
28461
+ const parentSingular = singularizeTable(parentTable);
28388
28462
  for (const [relKey, value] of Object.entries(btm)) {
28389
28463
  const cfg = value;
28390
28464
  if (!cfg || typeof cfg !== "object")
@@ -28397,7 +28471,7 @@ function buildMigrationPlan2(models, options) {
28397
28471
  const relatedModelName = cfg.model;
28398
28472
  const relatedRaw = models[relatedModelName];
28399
28473
  const relatedTable = relatedRaw?.table || `${String(relatedModelName).toLowerCase()}s`;
28400
- const relatedSingular = relatedTable.replace(/s$/, "");
28474
+ const relatedSingular = singularizeTable(relatedTable);
28401
28475
  const pivotTable = cfg.table || [parentSingular, relatedSingular].sort().join("_");
28402
28476
  const fkParent = cfg.foreignKey || `${parentSingular}_id`;
28403
28477
  const fkRelated = cfg.relatedKey || `${relatedSingular}_id`;
@@ -29015,14 +29089,14 @@ function defineSeeder(seederClass) {
29015
29089
  return seederClass;
29016
29090
  }
29017
29091
  // src/index.ts
29018
- var init_src2 = __esm(async () => {
29092
+ var init_src2 = __esm(() => {
29019
29093
  init_model2();
29020
29094
  init_model2();
29021
29095
  init_actions();
29022
29096
  init_browser();
29023
29097
  init_client();
29024
29098
  init_model2();
29025
- await init_config();
29099
+ init_config();
29026
29100
  init_drivers();
29027
29101
  init_dynamodb_client();
29028
29102
  init_dynamodb_single_table();
@@ -29031,10 +29105,11 @@ var init_src2 = __esm(async () => {
29031
29105
  init_loader();
29032
29106
  init_meta();
29033
29107
  init_migrations();
29108
+ init_pivot();
29034
29109
  init_orm();
29035
29110
  init_sqlite_pragmas();
29036
29111
  });
29037
- await init_src2();
29112
+ init_src2();
29038
29113
 
29039
29114
  export {
29040
29115
  dbWipe as wipeDatabase,
@@ -29046,10 +29121,12 @@ export {
29046
29121
  toDynamoDBItem,
29047
29122
  tinker,
29048
29123
  tableInfo,
29124
+ stripTrailingS,
29049
29125
  startConsole,
29050
29126
  stacksModelToEntity,
29051
29127
  sqlTypeToAttr,
29052
29128
  sql,
29129
+ singularizerFor,
29053
29130
  modelShow as showModel,
29054
29131
  setQueryCacheMaxSize,
29055
29132
  setConfig,
@@ -29089,6 +29166,7 @@ export {
29089
29166
  introspectDatabase,
29090
29167
  introspect,
29091
29168
  inspectTable,
29169
+ inflectSingular,
29092
29170
  importData,
29093
29171
  hashTableDefinition,
29094
29172
  hashMigrationPlan,