bun-query-builder 0.1.35 → 0.1.37

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
@@ -15036,7 +15036,7 @@ function createQueryBuilder(state) {
15036
15036
  return ensureBuilt().values();
15037
15037
  },
15038
15038
  toParams() {
15039
- return ensureBuilt().values?.() ?? [];
15039
+ return [...whereParams];
15040
15040
  },
15041
15041
  __rawState() {
15042
15042
  return { sql: reorderSelectClauses(text), params: [...whereParams] };
@@ -15133,265 +15133,247 @@ function createQueryBuilder(state) {
15133
15133
  throw new Error(`[query-builder] selectFromSub(...).${methodName}() is not supported. ` + `Apply ${methodName}() to the underlying subquery BEFORE passing it to selectFromSub, ` + `or use the regular selectFrom(...) builder. This previously silently returned without ` + `affecting the SQL, producing wrong results \u2014 see stacksjs/stacks#1862 #11.`);
15134
15134
  };
15135
15135
  }
15136
- const sql = _sql;
15137
- const q = _sql`SELECT * FROM (${sub.toSQL()}) AS ${_sql(alias)}`;
15138
- const base = {
15139
- distinct() {
15140
- const rest = String(q).replace(/^SELECT\s+/i, "");
15141
- const newQ = sql`SELECT DISTINCT ${sql``}${sql(rest)}`;
15142
- return createSubQueryBuilder(newQ);
15143
- },
15144
- distinctOn(...columns) {
15145
- const match = /^SELECT\s+(\S+)\s+FROM/i.exec(String(q));
15146
- const body = match ? `${match[1]} FROM` : String(q);
15147
- const newQ = sql`SELECT DISTINCT ON (${sql(columns)}) ${sql``}${sql(body)}`;
15148
- return createSubQueryBuilder(newQ);
15149
- },
15150
- selectRaw(fragment) {
15151
- const newQ = sql`${q} , ${fragment}`;
15152
- return createSubQueryBuilder(newQ);
15153
- },
15154
- where(expr, op, value) {
15155
- const newQ = applyWhereCondition(q, expr, op, value, "WHERE");
15156
- return createSubQueryBuilder(newQ);
15157
- },
15158
- andWhere(expr, op, value) {
15159
- const newQ = applyWhereCondition(q, expr, op, value, "AND");
15160
- return createSubQueryBuilder(newQ);
15161
- },
15162
- orWhere(expr, op, value) {
15163
- const newQ = applyWhereCondition(q, expr, op, value, "OR");
15164
- return createSubQueryBuilder(newQ);
15165
- },
15166
- orderBy(column, direction = "asc") {
15167
- const dir = direction === "asc" ? "ASC" : "DESC";
15168
- const current = String(q);
15169
- const newQ = SQL_PATTERNS.ORDER_BY.test(current) ? sql.unsafe(`${current}, ${column} ${dir}`) : sql`${q} ORDER BY ${sql(column)} ${direction === "asc" ? sql`ASC` : sql`DESC`}`;
15170
- return createSubQueryBuilder(newQ);
15171
- },
15172
- limit(n) {
15173
- const current = String(q);
15174
- const newQ = SQL_PATTERNS.LIMIT.test(current) ? sql.unsafe(current.replace(SQL_PATTERNS.LIMIT, ` LIMIT ${n}`)) : sql`${q} LIMIT ${n}`;
15175
- return createSubQueryBuilder(newQ);
15176
- },
15177
- offset(n) {
15178
- const current = String(q);
15179
- const newQ = SQL_PATTERNS.OFFSET.test(current) ? sql.unsafe(current.replace(SQL_PATTERNS.OFFSET, ` OFFSET ${n}`)) : sql`${q} OFFSET ${n}`;
15180
- return createSubQueryBuilder(newQ);
15181
- },
15182
- toSQL() {
15183
- return makeExecutableQuery(q);
15184
- },
15185
- async execute() {
15186
- return runWithHooks(q, "select");
15187
- },
15188
- async executeTakeFirst() {
15189
- const rows = await runWithHooks(q, "select");
15190
- return Array.isArray(rows) ? rows[0] : rows;
15191
- },
15192
- async executeTakeFirstOrThrow() {
15193
- const rows = await runWithHooks(q, "select");
15194
- const first = Array.isArray(rows) ? rows[0] : rows;
15195
- if (!first)
15196
- throw new Error("Record not found");
15197
- return first;
15198
- },
15199
- async get() {
15200
- return runWithHooks(q, "select");
15201
- },
15202
- async first() {
15203
- const rows = await runWithHooks(q, "select");
15204
- return Array.isArray(rows) ? rows[0] : rows;
15205
- },
15206
- async firstOrFail() {
15207
- const rows = await runWithHooks(q, "select");
15208
- const first = Array.isArray(rows) ? rows[0] : rows;
15209
- if (!first)
15210
- throw new Error("No rows found");
15211
- return first;
15212
- },
15213
- async exists() {
15214
- const countQ = sql`SELECT EXISTS(${q}) as exists`;
15215
- const result = await runWithHooks(countQ, "select");
15216
- return result?.[0]?.exists === true;
15217
- },
15218
- async doesntExist() {
15219
- const exists = await this.exists();
15220
- return !exists;
15221
- },
15222
- values() {
15223
- return q.values();
15224
- },
15225
- raw() {
15226
- return q.raw();
15227
- },
15228
- cancel() {
15229
- try {
15230
- q.cancel();
15231
- } catch {}
15232
- },
15233
- whereRaw: subqueryNotSupported("whereRaw"),
15234
- whereColumn: subqueryNotSupported("whereColumn"),
15235
- orWhereColumn: subqueryNotSupported("orWhereColumn"),
15236
- whereIn: subqueryNotSupported("whereIn"),
15237
- orWhereIn: subqueryNotSupported("orWhereIn"),
15238
- whereNotIn: subqueryNotSupported("whereNotIn"),
15239
- orWhereNotIn: subqueryNotSupported("orWhereNotIn"),
15240
- whereLike: subqueryNotSupported("whereLike"),
15241
- whereILike: subqueryNotSupported("whereILike"),
15242
- orWhereLike: subqueryNotSupported("orWhereLike"),
15243
- orWhereILike: subqueryNotSupported("orWhereILike"),
15244
- whereNotLike: subqueryNotSupported("whereNotLike"),
15245
- whereNotILike: subqueryNotSupported("whereNotILike"),
15246
- orWhereNotLike: subqueryNotSupported("orWhereNotLike"),
15247
- orWhereNotILike: subqueryNotSupported("orWhereNotILike"),
15248
- whereAny: subqueryNotSupported("whereAny"),
15249
- whereAll: subqueryNotSupported("whereAll"),
15250
- whereNone: subqueryNotSupported("whereNone"),
15251
- whereNested: subqueryNotSupported("whereNested"),
15252
- orWhereNested: subqueryNotSupported("orWhereNested"),
15253
- whereDate: subqueryNotSupported("whereDate"),
15254
- whereBetween: subqueryNotSupported("whereBetween"),
15255
- whereNotBetween: subqueryNotSupported("whereNotBetween"),
15256
- whereJsonContains: subqueryNotSupported("whereJsonContains"),
15257
- whereJsonPath: subqueryNotSupported("whereJsonPath"),
15258
- whereNull: subqueryNotSupported("whereNull"),
15259
- whereNotNull: subqueryNotSupported("whereNotNull"),
15260
- whereExists: subqueryNotSupported("whereExists"),
15261
- whereJsonDoesntContain: subqueryNotSupported("whereJsonDoesntContain"),
15262
- whereJsonContainsKey: subqueryNotSupported("whereJsonContainsKey"),
15263
- whereJsonDoesntContainKey: subqueryNotSupported("whereJsonDoesntContainKey"),
15264
- whereJsonLength: subqueryNotSupported("whereJsonLength"),
15265
- join: subqueryNotSupported("join"),
15266
- joinSub: subqueryNotSupported("joinSub"),
15267
- innerJoin: subqueryNotSupported("innerJoin"),
15268
- leftJoin: subqueryNotSupported("leftJoin"),
15269
- leftJoinSub: subqueryNotSupported("leftJoinSub"),
15270
- rightJoin: subqueryNotSupported("rightJoin"),
15271
- crossJoin: subqueryNotSupported("crossJoin"),
15272
- crossJoinSub: subqueryNotSupported("crossJoinSub"),
15273
- groupBy: subqueryNotSupported("groupBy"),
15274
- groupByRaw: subqueryNotSupported("groupByRaw"),
15275
- having: subqueryNotSupported("having"),
15276
- havingRaw: subqueryNotSupported("havingRaw"),
15277
- addSelect: subqueryNotSupported("addSelect"),
15278
- select: subqueryNotSupported("select"),
15279
- selectAll: subqueryNotSupported("selectAll"),
15280
- orderByDesc: subqueryNotSupported("orderByDesc"),
15281
- inRandomOrder: subqueryNotSupported("inRandomOrder"),
15282
- reorder: subqueryNotSupported("reorder"),
15283
- orderByRaw: subqueryNotSupported("orderByRaw"),
15284
- union: subqueryNotSupported("union"),
15285
- unionAll: subqueryNotSupported("unionAll"),
15286
- forPage: subqueryNotSupported("forPage"),
15287
- selectAllRelations: subqueryNotSupported("selectAllRelations"),
15288
- with: subqueryNotSupported("with"),
15289
- value: subqueryNotSupported("value"),
15290
- pluck: subqueryNotSupported("pluck"),
15291
- cursorPaginate: subqueryNotSupported("cursorPaginate"),
15292
- paginate: subqueryNotSupported("paginate"),
15293
- simplePaginate: subqueryNotSupported("simplePaginate"),
15294
- chunk: subqueryNotSupported("chunk"),
15295
- chunkById: subqueryNotSupported("chunkById"),
15296
- eachById: subqueryNotSupported("eachById"),
15297
- count: subqueryNotSupported("count"),
15298
- avg: subqueryNotSupported("avg"),
15299
- sum: subqueryNotSupported("sum"),
15300
- max: subqueryNotSupported("max"),
15301
- min: subqueryNotSupported("min"),
15302
- find: subqueryNotSupported("find"),
15303
- findOrFail: subqueryNotSupported("findOrFail"),
15304
- findMany: subqueryNotSupported("findMany"),
15305
- latest: subqueryNotSupported("latest"),
15306
- oldest: subqueryNotSupported("oldest"),
15307
- lazy: subqueryNotSupported("lazy"),
15308
- lazyById: subqueryNotSupported("lazyById"),
15309
- pipe: (fn) => fn(this),
15310
- when: subqueryNotSupported("when"),
15311
- tap: () => this,
15312
- dump: () => this,
15313
- dd: () => {
15314
- throw new Error("Dump and Die");
15315
- },
15316
- explain: () => Promise.resolve([]),
15317
- simple: () => q.simple(),
15318
- toText: () => String(q),
15319
- toParams: () => q.values?.() ?? [],
15320
- withTimeout: () => this,
15321
- abort: () => this,
15322
- lockForUpdate: () => this,
15323
- sharedLock: () => this,
15324
- withCTE: () => this,
15325
- withRecursive: () => this,
15326
- cache: () => this,
15327
- clone: () => this,
15328
- withTrashed: () => this,
15329
- onlyTrashed: () => this,
15330
- scope: () => this,
15331
- get rows() {
15332
- return [];
15333
- },
15334
- get row() {
15335
- return;
15336
- }
15136
+ validateIdentifier(String(alias), "selectFromSub(alias)");
15137
+ const rawState = typeof sub.__rawState === "function" ? sub.__rawState() : null;
15138
+ const subExec = sub.toSQL();
15139
+ const subText = rawState?.sql ?? (typeof subExec === "string" ? subExec : subExec?.sql ?? String(subExec));
15140
+ const subParams = rawState?.params ?? (Array.isArray(subExec?.values) ? subExec.values : []);
15141
+ const baseText = `SELECT * FROM (${subText}) AS ${String(alias)}`;
15142
+ const appendWhere = (text, params, hasWhere, expr, op, value, connector) => {
15143
+ const kw = !hasWhere ? "WHERE" : connector === "WHERE" ? "AND" : connector;
15144
+ const out = [...params];
15145
+ const cmp = (col, operator, val) => {
15146
+ validateIdentifier(col, "selectFromSub where(column)");
15147
+ const o = assertSafeWhereOperator(operator, "selectFromSub where(operator)");
15148
+ if (o === "in" || o === "not in") {
15149
+ const vals = Array.isArray(val) ? val : [val];
15150
+ const phs = getPlaceholders(vals.length, out.length + 1);
15151
+ out.push(...vals);
15152
+ return `${col} ${o.toUpperCase()} (${phs})`;
15153
+ }
15154
+ const ph = getPlaceholder(out.length + 1);
15155
+ out.push(val);
15156
+ return `${col} ${o} ${ph}`;
15157
+ };
15158
+ let clause;
15159
+ if (typeof expr === "string" && op !== undefined)
15160
+ clause = cmp(expr, op, value);
15161
+ else if (Array.isArray(expr) && expr.length === 3)
15162
+ clause = cmp(expr[0], expr[1], expr[2]);
15163
+ else if (expr && typeof expr === "object")
15164
+ clause = Object.entries(expr).map(([k, v]) => cmp(k, "=", v)).join(" AND ");
15165
+ else
15166
+ return { text, params };
15167
+ return { text: `${text} ${kw} ${clause}`, params: out };
15337
15168
  };
15338
- function createSubQueryBuilder(newQ) {
15339
- return {
15340
- ...base,
15341
- toSQL: () => makeExecutableQuery(newQ),
15342
- execute: () => runWithHooks(newQ, "select"),
15343
- get: () => runWithHooks(newQ, "select"),
15344
- first: async () => {
15345
- const rows = await runWithHooks(newQ, "select");
15169
+ function makeSub(text, params, hasWhere = false) {
15170
+ const build = () => params.length > 0 ? _sql.unsafe(text, params) : _sql.unsafe(text);
15171
+ const base = {
15172
+ distinct() {
15173
+ return makeSub(text.replace(/^SELECT\s+/i, "SELECT DISTINCT "), params, hasWhere);
15174
+ },
15175
+ distinctOn(...columns) {
15176
+ const cols = columns.map(String).join(", ");
15177
+ return makeSub(text.replace(/^SELECT\s+/i, `SELECT DISTINCT ON (${cols}) `), params, hasWhere);
15178
+ },
15179
+ selectRaw(fragment) {
15180
+ const frag = renderRawFragment(fragment, "selectFromSub.selectRaw(fragment)");
15181
+ const fromIdx = text.indexOf(" FROM ");
15182
+ const newText = fromIdx !== -1 ? `${text.slice(0, fromIdx)}, ${frag}${text.slice(fromIdx)}` : `${text}, ${frag}`;
15183
+ return makeSub(newText, params, hasWhere);
15184
+ },
15185
+ where(expr, op, value) {
15186
+ const r = appendWhere(text, params, hasWhere, expr, op, value, "WHERE");
15187
+ return makeSub(r.text, r.params, true);
15188
+ },
15189
+ andWhere(expr, op, value) {
15190
+ const r = appendWhere(text, params, hasWhere, expr, op, value, "AND");
15191
+ return makeSub(r.text, r.params, true);
15192
+ },
15193
+ orWhere(expr, op, value) {
15194
+ const r = appendWhere(text, params, hasWhere, expr, op, value, "OR");
15195
+ return makeSub(r.text, r.params, true);
15196
+ },
15197
+ orderBy(column, direction = "asc") {
15198
+ validateIdentifier(String(column), "selectFromSub.orderBy(column)");
15199
+ const dir = direction === "asc" ? "ASC" : "DESC";
15200
+ const newText = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${column} ${dir}` : `${text} ORDER BY ${column} ${dir}`;
15201
+ return makeSub(newText, params, hasWhere);
15202
+ },
15203
+ limit(n) {
15204
+ if (!Number.isInteger(n) || n < 0)
15205
+ throw new TypeError(`[query-builder] selectFromSub.limit(n): expected non-negative integer, got ${n}`);
15206
+ const newText = SQL_PATTERNS.LIMIT.test(text) ? text.replace(SQL_PATTERNS.LIMIT, ` LIMIT ${n}`) : `${text} LIMIT ${n}`;
15207
+ return makeSub(newText, params, hasWhere);
15208
+ },
15209
+ offset(n) {
15210
+ if (!Number.isInteger(n) || n < 0)
15211
+ throw new TypeError(`[query-builder] selectFromSub.offset(n): expected non-negative integer, got ${n}`);
15212
+ const newText = SQL_PATTERNS.OFFSET.test(text) ? text.replace(SQL_PATTERNS.OFFSET, ` OFFSET ${n}`) : `${text} OFFSET ${n}`;
15213
+ return makeSub(newText, params, hasWhere);
15214
+ },
15215
+ toSQL() {
15216
+ return makeExecutableQuery(build(), text);
15217
+ },
15218
+ async execute() {
15219
+ return runWithHooks(build(), "select");
15220
+ },
15221
+ async executeTakeFirst() {
15222
+ const rows = await runWithHooks(build(), "select");
15346
15223
  return Array.isArray(rows) ? rows[0] : rows;
15347
15224
  },
15348
- firstOrFail: async () => {
15349
- const rows = await runWithHooks(newQ, "select");
15225
+ async executeTakeFirstOrThrow() {
15226
+ const rows = await runWithHooks(build(), "select");
15227
+ const first = Array.isArray(rows) ? rows[0] : rows;
15228
+ if (!first)
15229
+ throw new Error("Record not found");
15230
+ return first;
15231
+ },
15232
+ async get() {
15233
+ return runWithHooks(build(), "select");
15234
+ },
15235
+ async first() {
15236
+ const rows = await runWithHooks(build(), "select");
15237
+ return Array.isArray(rows) ? rows[0] : rows;
15238
+ },
15239
+ async firstOrFail() {
15240
+ const rows = await runWithHooks(build(), "select");
15350
15241
  const first = Array.isArray(rows) ? rows[0] : rows;
15351
15242
  if (!first)
15352
15243
  throw new Error("No rows found");
15353
15244
  return first;
15354
15245
  },
15355
- exists: async () => {
15356
- const countQ = sql`SELECT EXISTS(${newQ}) as exists`;
15357
- const result = await runWithHooks(countQ, "select");
15358
- return result?.[0]?.exists === true;
15246
+ async count() {
15247
+ const q = params.length > 0 ? _sql.unsafe(`SELECT COUNT(*) as c FROM (${text}) as sub`, params) : _sql.unsafe(`SELECT COUNT(*) as c FROM (${text}) as sub`);
15248
+ const rows = await runWithHooks(q, "select");
15249
+ return Number(rows?.[0]?.c ?? 0);
15250
+ },
15251
+ async exists() {
15252
+ const q = params.length > 0 ? _sql.unsafe(`SELECT EXISTS(${text}) as e`, params) : _sql.unsafe(`SELECT EXISTS(${text}) as e`);
15253
+ const result = await runWithHooks(q, "select");
15254
+ return Boolean(result?.[0]?.e);
15359
15255
  },
15360
- doesntExist: async () => {
15361
- const exists = await base.exists();
15362
- return !exists;
15256
+ async doesntExist() {
15257
+ return !await base.exists();
15363
15258
  },
15364
- values: () => newQ.values(),
15365
- raw: () => newQ.raw(),
15366
- cancel: () => {
15259
+ values() {
15260
+ return build().values();
15261
+ },
15262
+ raw() {
15263
+ return build().raw();
15264
+ },
15265
+ cancel() {
15367
15266
  try {
15368
- newQ.cancel();
15267
+ build().cancel();
15369
15268
  } catch {}
15370
15269
  },
15371
- simple: () => newQ.simple(),
15372
- toText: () => String(newQ),
15373
- toParams: () => newQ.values?.() ?? []
15270
+ whereRaw: subqueryNotSupported("whereRaw"),
15271
+ whereColumn: subqueryNotSupported("whereColumn"),
15272
+ orWhereColumn: subqueryNotSupported("orWhereColumn"),
15273
+ whereIn: subqueryNotSupported("whereIn"),
15274
+ orWhereIn: subqueryNotSupported("orWhereIn"),
15275
+ whereNotIn: subqueryNotSupported("whereNotIn"),
15276
+ orWhereNotIn: subqueryNotSupported("orWhereNotIn"),
15277
+ whereLike: subqueryNotSupported("whereLike"),
15278
+ whereILike: subqueryNotSupported("whereILike"),
15279
+ orWhereLike: subqueryNotSupported("orWhereLike"),
15280
+ orWhereILike: subqueryNotSupported("orWhereILike"),
15281
+ whereNotLike: subqueryNotSupported("whereNotLike"),
15282
+ whereNotILike: subqueryNotSupported("whereNotILike"),
15283
+ orWhereNotLike: subqueryNotSupported("orWhereNotLike"),
15284
+ orWhereNotILike: subqueryNotSupported("orWhereNotILike"),
15285
+ whereAny: subqueryNotSupported("whereAny"),
15286
+ whereAll: subqueryNotSupported("whereAll"),
15287
+ whereNone: subqueryNotSupported("whereNone"),
15288
+ whereNested: subqueryNotSupported("whereNested"),
15289
+ orWhereNested: subqueryNotSupported("orWhereNested"),
15290
+ whereDate: subqueryNotSupported("whereDate"),
15291
+ whereBetween: subqueryNotSupported("whereBetween"),
15292
+ whereNotBetween: subqueryNotSupported("whereNotBetween"),
15293
+ whereJsonContains: subqueryNotSupported("whereJsonContains"),
15294
+ whereJsonPath: subqueryNotSupported("whereJsonPath"),
15295
+ whereNull: subqueryNotSupported("whereNull"),
15296
+ whereNotNull: subqueryNotSupported("whereNotNull"),
15297
+ whereExists: subqueryNotSupported("whereExists"),
15298
+ whereJsonDoesntContain: subqueryNotSupported("whereJsonDoesntContain"),
15299
+ whereJsonContainsKey: subqueryNotSupported("whereJsonContainsKey"),
15300
+ whereJsonDoesntContainKey: subqueryNotSupported("whereJsonDoesntContainKey"),
15301
+ whereJsonLength: subqueryNotSupported("whereJsonLength"),
15302
+ join: subqueryNotSupported("join"),
15303
+ joinSub: subqueryNotSupported("joinSub"),
15304
+ innerJoin: subqueryNotSupported("innerJoin"),
15305
+ leftJoin: subqueryNotSupported("leftJoin"),
15306
+ leftJoinSub: subqueryNotSupported("leftJoinSub"),
15307
+ rightJoin: subqueryNotSupported("rightJoin"),
15308
+ crossJoin: subqueryNotSupported("crossJoin"),
15309
+ crossJoinSub: subqueryNotSupported("crossJoinSub"),
15310
+ groupBy: subqueryNotSupported("groupBy"),
15311
+ groupByRaw: subqueryNotSupported("groupByRaw"),
15312
+ having: subqueryNotSupported("having"),
15313
+ havingRaw: subqueryNotSupported("havingRaw"),
15314
+ addSelect: subqueryNotSupported("addSelect"),
15315
+ select: subqueryNotSupported("select"),
15316
+ selectAll: subqueryNotSupported("selectAll"),
15317
+ orderByDesc: subqueryNotSupported("orderByDesc"),
15318
+ inRandomOrder: subqueryNotSupported("inRandomOrder"),
15319
+ reorder: subqueryNotSupported("reorder"),
15320
+ orderByRaw: subqueryNotSupported("orderByRaw"),
15321
+ union: subqueryNotSupported("union"),
15322
+ unionAll: subqueryNotSupported("unionAll"),
15323
+ forPage: subqueryNotSupported("forPage"),
15324
+ selectAllRelations: subqueryNotSupported("selectAllRelations"),
15325
+ with: subqueryNotSupported("with"),
15326
+ value: subqueryNotSupported("value"),
15327
+ pluck: subqueryNotSupported("pluck"),
15328
+ cursorPaginate: subqueryNotSupported("cursorPaginate"),
15329
+ paginate: subqueryNotSupported("paginate"),
15330
+ simplePaginate: subqueryNotSupported("simplePaginate"),
15331
+ chunk: subqueryNotSupported("chunk"),
15332
+ chunkById: subqueryNotSupported("chunkById"),
15333
+ eachById: subqueryNotSupported("eachById"),
15334
+ avg: subqueryNotSupported("avg"),
15335
+ sum: subqueryNotSupported("sum"),
15336
+ max: subqueryNotSupported("max"),
15337
+ min: subqueryNotSupported("min"),
15338
+ find: subqueryNotSupported("find"),
15339
+ findOrFail: subqueryNotSupported("findOrFail"),
15340
+ findMany: subqueryNotSupported("findMany"),
15341
+ latest: subqueryNotSupported("latest"),
15342
+ oldest: subqueryNotSupported("oldest"),
15343
+ lazy: subqueryNotSupported("lazy"),
15344
+ lazyById: subqueryNotSupported("lazyById"),
15345
+ pipe: (fn) => fn(base),
15346
+ when: subqueryNotSupported("when"),
15347
+ tap: () => base,
15348
+ dump: () => base,
15349
+ dd: () => {
15350
+ throw new Error("Dump and Die");
15351
+ },
15352
+ explain: () => Promise.resolve([]),
15353
+ simple: () => build().simple(),
15354
+ toText: () => text,
15355
+ toParams: () => [...params],
15356
+ withTimeout: () => base,
15357
+ abort: () => base,
15358
+ lockForUpdate: () => base,
15359
+ sharedLock: () => base,
15360
+ withCTE: () => base,
15361
+ withRecursive: () => base,
15362
+ cache: () => base,
15363
+ clone: () => base,
15364
+ withTrashed: () => base,
15365
+ onlyTrashed: () => base,
15366
+ scope: () => base,
15367
+ get rows() {
15368
+ return [];
15369
+ },
15370
+ get row() {
15371
+ return;
15372
+ }
15374
15373
  };
15374
+ return base;
15375
15375
  }
15376
- function applyWhereCondition(query, expr, op, value, prefix = "WHERE") {
15377
- if (typeof expr === "string" && op !== undefined) {
15378
- const clause = Array.isArray(value) ? `${expr} IN (?)` : `${expr} ${op} ?`;
15379
- return sql`${query} ${sql(prefix)} ${sql(clause)}`;
15380
- } else if (Array.isArray(expr) && expr.length === 3) {
15381
- const [column, operator, val] = expr;
15382
- const clause = Array.isArray(val) ? `${column} IN (?)` : `${column} ${operator} ?`;
15383
- return sql`${query} ${sql(prefix)} ${sql(clause)}`;
15384
- } else if (typeof expr === "object" && expr !== null) {
15385
- const conditions = Object.entries(expr).map(([key, val]) => {
15386
- const clause = Array.isArray(val) ? `${key} IN (?)` : `${key} = ?`;
15387
- return sql`${sql(clause)}`;
15388
- });
15389
- const combined = conditions.reduce((acc, cond, i) => i === 0 ? cond : sql`${acc} AND ${cond}`);
15390
- return sql`${query} ${sql(prefix)} ${combined}`;
15391
- }
15392
- return query;
15393
- }
15394
- return base;
15376
+ return makeSub(baseText, subParams);
15395
15377
  },
15396
15378
  insertInto(table) {
15397
15379
  let built;
@@ -24639,8 +24621,45 @@ function resolveRelation(definition, relationName) {
24639
24621
  pivotTimestamps: Boolean(config6?.pivot?.timestamps)
24640
24622
  };
24641
24623
  }
24624
+ const resolveThrough = (field, type) => {
24625
+ if (!field || typeof field !== "object" || Array.isArray(field))
24626
+ return null;
24627
+ const entry = field[relationName] ?? field[Object.keys(field).find((k2) => k2.toLowerCase() === relationName.toLowerCase()) ?? ""];
24628
+ if (!entry || typeof entry !== "object")
24629
+ return null;
24630
+ const throughModelName = entry.through;
24631
+ const targetModelName = entry.target;
24632
+ if (!throughModelName || !targetModelName)
24633
+ return null;
24634
+ const throughModel = getModelFromRegistry(throughModelName);
24635
+ const throughDef = throughModel?.getDefinition?.() || throughModel?.definition;
24636
+ const throughTable = throughModel?.getTable?.() || throughDef?.table || toTableName(throughModelName);
24637
+ const throughPk = throughDef?.primaryKey || "id";
24638
+ const targetModel = getModelFromRegistry(targetModelName);
24639
+ const targetTable = targetModel?.getTable?.() || toTableName(targetModelName);
24640
+ return {
24641
+ type,
24642
+ relatedModelName: targetModelName,
24643
+ relatedTable: targetTable,
24644
+ foreignKey: `${toSnakeCase(parentName)}_id`,
24645
+ localKey: parentPk,
24646
+ throughTable,
24647
+ throughForeignKey: `${toSnakeCase(parentName)}_id`,
24648
+ throughLocalKey: throughPk,
24649
+ targetForeignKey: `${singularizeWord(throughTable)}_id`
24650
+ };
24651
+ };
24652
+ const hmt = resolveThrough(definition.hasManyThrough, "hasManyThrough");
24653
+ if (hmt)
24654
+ return hmt;
24655
+ const hot = resolveThrough(definition.hasOneThrough, "hasOneThrough");
24656
+ if (hot)
24657
+ return hot;
24642
24658
  return null;
24643
24659
  }
24660
+ function singularizeWord(table) {
24661
+ return table.endsWith("s") ? table.slice(0, -1) : table;
24662
+ }
24644
24663
 
24645
24664
  class BelongsToManyRelationBuilder {
24646
24665
  _parent;
@@ -25277,6 +25296,40 @@ class ModelQueryBuilder {
25277
25296
  instance.setRelation(relationName, relatedInstances);
25278
25297
  }
25279
25298
  }
25299
+ if (rel.type === "hasManyThrough" || rel.type === "hasOneThrough") {
25300
+ const parentIds = instances.map((i2) => i2.get(pk)).filter((id) => id != null);
25301
+ if (parentIds.length === 0)
25302
+ continue;
25303
+ const throughPk = rel.throughLocalKey || "id";
25304
+ const throughPh = parentIds.map(() => "?").join(", ");
25305
+ const throughRows = await exec.all(`SELECT ${throughPk}, ${rel.throughForeignKey} FROM ${rel.throughTable} WHERE ${rel.throughForeignKey} IN (${throughPh})`, parentIds);
25306
+ if (throughRows.length === 0) {
25307
+ for (const instance of instances)
25308
+ instance.setRelation(relationName, rel.type === "hasManyThrough" ? [] : null);
25309
+ continue;
25310
+ }
25311
+ const throughToParent = new Map;
25312
+ for (const t2 of throughRows)
25313
+ throughToParent.set(t2[throughPk], t2[rel.throughForeignKey]);
25314
+ const throughIds = [...new Set(throughRows.map((t2) => t2[throughPk]))];
25315
+ const targetPh = throughIds.map(() => "?").join(", ");
25316
+ const targetRows = await exec.all(`SELECT * FROM ${rel.relatedTable} WHERE ${rel.targetForeignKey} IN (${targetPh})`, throughIds);
25317
+ const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
25318
+ const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
25319
+ const byParent = new Map;
25320
+ for (const row of targetRows) {
25321
+ const parentVal = throughToParent.get(row[rel.targetForeignKey]);
25322
+ if (parentVal == null)
25323
+ continue;
25324
+ if (!byParent.has(parentVal))
25325
+ byParent.set(parentVal, []);
25326
+ byParent.get(parentVal).push(new ModelInstance(relDef, row));
25327
+ }
25328
+ for (const instance of instances) {
25329
+ const group = byParent.get(instance.get(pk)) || [];
25330
+ instance.setRelation(relationName, rel.type === "hasManyThrough" ? group : group[0] ?? null);
25331
+ }
25332
+ }
25280
25333
  }
25281
25334
  }
25282
25335
  async get() {
@@ -30153,7 +30206,7 @@ function getPrefix() {
30153
30206
  }
30154
30207
  var prefix = getPrefix();
30155
30208
  // package.json
30156
- var version2 = "0.1.35";
30209
+ var version2 = "0.1.37";
30157
30210
 
30158
30211
  // bin/cli.ts
30159
30212
  init_actions();
package/dist/orm.d.ts CHANGED
@@ -163,11 +163,15 @@ declare interface OrmExecutor {
163
163
  * populated only for `belongsToMany` relations.
164
164
  */
165
165
  declare interface ResolvedRelation {
166
- type: 'hasMany' | 'hasOne' | 'belongsTo' | 'belongsToMany'
166
+ type: 'hasMany' | 'hasOne' | 'belongsTo' | 'belongsToMany' | 'hasManyThrough' | 'hasOneThrough'
167
167
  relatedModelName: string
168
168
  relatedTable: string
169
169
  foreignKey: string
170
170
  localKey: string
171
+ throughTable?: string
172
+ throughForeignKey?: string
173
+ throughLocalKey?: string
174
+ targetForeignKey?: string
171
175
  pivotTable?: string
172
176
  pivotFkParent?: string
173
177
  pivotFkRelated?: string
package/dist/src/index.js CHANGED
@@ -15036,7 +15036,7 @@ function createQueryBuilder(state) {
15036
15036
  return ensureBuilt().values();
15037
15037
  },
15038
15038
  toParams() {
15039
- return ensureBuilt().values?.() ?? [];
15039
+ return [...whereParams];
15040
15040
  },
15041
15041
  __rawState() {
15042
15042
  return { sql: reorderSelectClauses(text), params: [...whereParams] };
@@ -15133,265 +15133,247 @@ function createQueryBuilder(state) {
15133
15133
  throw new Error(`[query-builder] selectFromSub(...).${methodName}() is not supported. ` + `Apply ${methodName}() to the underlying subquery BEFORE passing it to selectFromSub, ` + `or use the regular selectFrom(...) builder. This previously silently returned without ` + `affecting the SQL, producing wrong results \u2014 see stacksjs/stacks#1862 #11.`);
15134
15134
  };
15135
15135
  }
15136
- const sql = _sql;
15137
- const q = _sql`SELECT * FROM (${sub.toSQL()}) AS ${_sql(alias)}`;
15138
- const base = {
15139
- distinct() {
15140
- const rest = String(q).replace(/^SELECT\s+/i, "");
15141
- const newQ = sql`SELECT DISTINCT ${sql``}${sql(rest)}`;
15142
- return createSubQueryBuilder(newQ);
15143
- },
15144
- distinctOn(...columns) {
15145
- const match = /^SELECT\s+(\S+)\s+FROM/i.exec(String(q));
15146
- const body = match ? `${match[1]} FROM` : String(q);
15147
- const newQ = sql`SELECT DISTINCT ON (${sql(columns)}) ${sql``}${sql(body)}`;
15148
- return createSubQueryBuilder(newQ);
15149
- },
15150
- selectRaw(fragment) {
15151
- const newQ = sql`${q} , ${fragment}`;
15152
- return createSubQueryBuilder(newQ);
15153
- },
15154
- where(expr, op, value) {
15155
- const newQ = applyWhereCondition(q, expr, op, value, "WHERE");
15156
- return createSubQueryBuilder(newQ);
15157
- },
15158
- andWhere(expr, op, value) {
15159
- const newQ = applyWhereCondition(q, expr, op, value, "AND");
15160
- return createSubQueryBuilder(newQ);
15161
- },
15162
- orWhere(expr, op, value) {
15163
- const newQ = applyWhereCondition(q, expr, op, value, "OR");
15164
- return createSubQueryBuilder(newQ);
15165
- },
15166
- orderBy(column, direction = "asc") {
15167
- const dir = direction === "asc" ? "ASC" : "DESC";
15168
- const current = String(q);
15169
- const newQ = SQL_PATTERNS.ORDER_BY.test(current) ? sql.unsafe(`${current}, ${column} ${dir}`) : sql`${q} ORDER BY ${sql(column)} ${direction === "asc" ? sql`ASC` : sql`DESC`}`;
15170
- return createSubQueryBuilder(newQ);
15171
- },
15172
- limit(n) {
15173
- const current = String(q);
15174
- const newQ = SQL_PATTERNS.LIMIT.test(current) ? sql.unsafe(current.replace(SQL_PATTERNS.LIMIT, ` LIMIT ${n}`)) : sql`${q} LIMIT ${n}`;
15175
- return createSubQueryBuilder(newQ);
15176
- },
15177
- offset(n) {
15178
- const current = String(q);
15179
- const newQ = SQL_PATTERNS.OFFSET.test(current) ? sql.unsafe(current.replace(SQL_PATTERNS.OFFSET, ` OFFSET ${n}`)) : sql`${q} OFFSET ${n}`;
15180
- return createSubQueryBuilder(newQ);
15181
- },
15182
- toSQL() {
15183
- return makeExecutableQuery(q);
15184
- },
15185
- async execute() {
15186
- return runWithHooks(q, "select");
15187
- },
15188
- async executeTakeFirst() {
15189
- const rows = await runWithHooks(q, "select");
15190
- return Array.isArray(rows) ? rows[0] : rows;
15191
- },
15192
- async executeTakeFirstOrThrow() {
15193
- const rows = await runWithHooks(q, "select");
15194
- const first = Array.isArray(rows) ? rows[0] : rows;
15195
- if (!first)
15196
- throw new Error("Record not found");
15197
- return first;
15198
- },
15199
- async get() {
15200
- return runWithHooks(q, "select");
15201
- },
15202
- async first() {
15203
- const rows = await runWithHooks(q, "select");
15204
- return Array.isArray(rows) ? rows[0] : rows;
15205
- },
15206
- async firstOrFail() {
15207
- const rows = await runWithHooks(q, "select");
15208
- const first = Array.isArray(rows) ? rows[0] : rows;
15209
- if (!first)
15210
- throw new Error("No rows found");
15211
- return first;
15212
- },
15213
- async exists() {
15214
- const countQ = sql`SELECT EXISTS(${q}) as exists`;
15215
- const result = await runWithHooks(countQ, "select");
15216
- return result?.[0]?.exists === true;
15217
- },
15218
- async doesntExist() {
15219
- const exists = await this.exists();
15220
- return !exists;
15221
- },
15222
- values() {
15223
- return q.values();
15224
- },
15225
- raw() {
15226
- return q.raw();
15227
- },
15228
- cancel() {
15229
- try {
15230
- q.cancel();
15231
- } catch {}
15232
- },
15233
- whereRaw: subqueryNotSupported("whereRaw"),
15234
- whereColumn: subqueryNotSupported("whereColumn"),
15235
- orWhereColumn: subqueryNotSupported("orWhereColumn"),
15236
- whereIn: subqueryNotSupported("whereIn"),
15237
- orWhereIn: subqueryNotSupported("orWhereIn"),
15238
- whereNotIn: subqueryNotSupported("whereNotIn"),
15239
- orWhereNotIn: subqueryNotSupported("orWhereNotIn"),
15240
- whereLike: subqueryNotSupported("whereLike"),
15241
- whereILike: subqueryNotSupported("whereILike"),
15242
- orWhereLike: subqueryNotSupported("orWhereLike"),
15243
- orWhereILike: subqueryNotSupported("orWhereILike"),
15244
- whereNotLike: subqueryNotSupported("whereNotLike"),
15245
- whereNotILike: subqueryNotSupported("whereNotILike"),
15246
- orWhereNotLike: subqueryNotSupported("orWhereNotLike"),
15247
- orWhereNotILike: subqueryNotSupported("orWhereNotILike"),
15248
- whereAny: subqueryNotSupported("whereAny"),
15249
- whereAll: subqueryNotSupported("whereAll"),
15250
- whereNone: subqueryNotSupported("whereNone"),
15251
- whereNested: subqueryNotSupported("whereNested"),
15252
- orWhereNested: subqueryNotSupported("orWhereNested"),
15253
- whereDate: subqueryNotSupported("whereDate"),
15254
- whereBetween: subqueryNotSupported("whereBetween"),
15255
- whereNotBetween: subqueryNotSupported("whereNotBetween"),
15256
- whereJsonContains: subqueryNotSupported("whereJsonContains"),
15257
- whereJsonPath: subqueryNotSupported("whereJsonPath"),
15258
- whereNull: subqueryNotSupported("whereNull"),
15259
- whereNotNull: subqueryNotSupported("whereNotNull"),
15260
- whereExists: subqueryNotSupported("whereExists"),
15261
- whereJsonDoesntContain: subqueryNotSupported("whereJsonDoesntContain"),
15262
- whereJsonContainsKey: subqueryNotSupported("whereJsonContainsKey"),
15263
- whereJsonDoesntContainKey: subqueryNotSupported("whereJsonDoesntContainKey"),
15264
- whereJsonLength: subqueryNotSupported("whereJsonLength"),
15265
- join: subqueryNotSupported("join"),
15266
- joinSub: subqueryNotSupported("joinSub"),
15267
- innerJoin: subqueryNotSupported("innerJoin"),
15268
- leftJoin: subqueryNotSupported("leftJoin"),
15269
- leftJoinSub: subqueryNotSupported("leftJoinSub"),
15270
- rightJoin: subqueryNotSupported("rightJoin"),
15271
- crossJoin: subqueryNotSupported("crossJoin"),
15272
- crossJoinSub: subqueryNotSupported("crossJoinSub"),
15273
- groupBy: subqueryNotSupported("groupBy"),
15274
- groupByRaw: subqueryNotSupported("groupByRaw"),
15275
- having: subqueryNotSupported("having"),
15276
- havingRaw: subqueryNotSupported("havingRaw"),
15277
- addSelect: subqueryNotSupported("addSelect"),
15278
- select: subqueryNotSupported("select"),
15279
- selectAll: subqueryNotSupported("selectAll"),
15280
- orderByDesc: subqueryNotSupported("orderByDesc"),
15281
- inRandomOrder: subqueryNotSupported("inRandomOrder"),
15282
- reorder: subqueryNotSupported("reorder"),
15283
- orderByRaw: subqueryNotSupported("orderByRaw"),
15284
- union: subqueryNotSupported("union"),
15285
- unionAll: subqueryNotSupported("unionAll"),
15286
- forPage: subqueryNotSupported("forPage"),
15287
- selectAllRelations: subqueryNotSupported("selectAllRelations"),
15288
- with: subqueryNotSupported("with"),
15289
- value: subqueryNotSupported("value"),
15290
- pluck: subqueryNotSupported("pluck"),
15291
- cursorPaginate: subqueryNotSupported("cursorPaginate"),
15292
- paginate: subqueryNotSupported("paginate"),
15293
- simplePaginate: subqueryNotSupported("simplePaginate"),
15294
- chunk: subqueryNotSupported("chunk"),
15295
- chunkById: subqueryNotSupported("chunkById"),
15296
- eachById: subqueryNotSupported("eachById"),
15297
- count: subqueryNotSupported("count"),
15298
- avg: subqueryNotSupported("avg"),
15299
- sum: subqueryNotSupported("sum"),
15300
- max: subqueryNotSupported("max"),
15301
- min: subqueryNotSupported("min"),
15302
- find: subqueryNotSupported("find"),
15303
- findOrFail: subqueryNotSupported("findOrFail"),
15304
- findMany: subqueryNotSupported("findMany"),
15305
- latest: subqueryNotSupported("latest"),
15306
- oldest: subqueryNotSupported("oldest"),
15307
- lazy: subqueryNotSupported("lazy"),
15308
- lazyById: subqueryNotSupported("lazyById"),
15309
- pipe: (fn) => fn(this),
15310
- when: subqueryNotSupported("when"),
15311
- tap: () => this,
15312
- dump: () => this,
15313
- dd: () => {
15314
- throw new Error("Dump and Die");
15315
- },
15316
- explain: () => Promise.resolve([]),
15317
- simple: () => q.simple(),
15318
- toText: () => String(q),
15319
- toParams: () => q.values?.() ?? [],
15320
- withTimeout: () => this,
15321
- abort: () => this,
15322
- lockForUpdate: () => this,
15323
- sharedLock: () => this,
15324
- withCTE: () => this,
15325
- withRecursive: () => this,
15326
- cache: () => this,
15327
- clone: () => this,
15328
- withTrashed: () => this,
15329
- onlyTrashed: () => this,
15330
- scope: () => this,
15331
- get rows() {
15332
- return [];
15333
- },
15334
- get row() {
15335
- return;
15336
- }
15136
+ validateIdentifier(String(alias), "selectFromSub(alias)");
15137
+ const rawState = typeof sub.__rawState === "function" ? sub.__rawState() : null;
15138
+ const subExec = sub.toSQL();
15139
+ const subText = rawState?.sql ?? (typeof subExec === "string" ? subExec : subExec?.sql ?? String(subExec));
15140
+ const subParams = rawState?.params ?? (Array.isArray(subExec?.values) ? subExec.values : []);
15141
+ const baseText = `SELECT * FROM (${subText}) AS ${String(alias)}`;
15142
+ const appendWhere = (text, params, hasWhere, expr, op, value, connector) => {
15143
+ const kw = !hasWhere ? "WHERE" : connector === "WHERE" ? "AND" : connector;
15144
+ const out = [...params];
15145
+ const cmp = (col, operator, val) => {
15146
+ validateIdentifier(col, "selectFromSub where(column)");
15147
+ const o = assertSafeWhereOperator(operator, "selectFromSub where(operator)");
15148
+ if (o === "in" || o === "not in") {
15149
+ const vals = Array.isArray(val) ? val : [val];
15150
+ const phs = getPlaceholders(vals.length, out.length + 1);
15151
+ out.push(...vals);
15152
+ return `${col} ${o.toUpperCase()} (${phs})`;
15153
+ }
15154
+ const ph = getPlaceholder(out.length + 1);
15155
+ out.push(val);
15156
+ return `${col} ${o} ${ph}`;
15157
+ };
15158
+ let clause;
15159
+ if (typeof expr === "string" && op !== undefined)
15160
+ clause = cmp(expr, op, value);
15161
+ else if (Array.isArray(expr) && expr.length === 3)
15162
+ clause = cmp(expr[0], expr[1], expr[2]);
15163
+ else if (expr && typeof expr === "object")
15164
+ clause = Object.entries(expr).map(([k, v]) => cmp(k, "=", v)).join(" AND ");
15165
+ else
15166
+ return { text, params };
15167
+ return { text: `${text} ${kw} ${clause}`, params: out };
15337
15168
  };
15338
- function createSubQueryBuilder(newQ) {
15339
- return {
15340
- ...base,
15341
- toSQL: () => makeExecutableQuery(newQ),
15342
- execute: () => runWithHooks(newQ, "select"),
15343
- get: () => runWithHooks(newQ, "select"),
15344
- first: async () => {
15345
- const rows = await runWithHooks(newQ, "select");
15169
+ function makeSub(text, params, hasWhere = false) {
15170
+ const build = () => params.length > 0 ? _sql.unsafe(text, params) : _sql.unsafe(text);
15171
+ const base = {
15172
+ distinct() {
15173
+ return makeSub(text.replace(/^SELECT\s+/i, "SELECT DISTINCT "), params, hasWhere);
15174
+ },
15175
+ distinctOn(...columns) {
15176
+ const cols = columns.map(String).join(", ");
15177
+ return makeSub(text.replace(/^SELECT\s+/i, `SELECT DISTINCT ON (${cols}) `), params, hasWhere);
15178
+ },
15179
+ selectRaw(fragment) {
15180
+ const frag = renderRawFragment(fragment, "selectFromSub.selectRaw(fragment)");
15181
+ const fromIdx = text.indexOf(" FROM ");
15182
+ const newText = fromIdx !== -1 ? `${text.slice(0, fromIdx)}, ${frag}${text.slice(fromIdx)}` : `${text}, ${frag}`;
15183
+ return makeSub(newText, params, hasWhere);
15184
+ },
15185
+ where(expr, op, value) {
15186
+ const r = appendWhere(text, params, hasWhere, expr, op, value, "WHERE");
15187
+ return makeSub(r.text, r.params, true);
15188
+ },
15189
+ andWhere(expr, op, value) {
15190
+ const r = appendWhere(text, params, hasWhere, expr, op, value, "AND");
15191
+ return makeSub(r.text, r.params, true);
15192
+ },
15193
+ orWhere(expr, op, value) {
15194
+ const r = appendWhere(text, params, hasWhere, expr, op, value, "OR");
15195
+ return makeSub(r.text, r.params, true);
15196
+ },
15197
+ orderBy(column, direction = "asc") {
15198
+ validateIdentifier(String(column), "selectFromSub.orderBy(column)");
15199
+ const dir = direction === "asc" ? "ASC" : "DESC";
15200
+ const newText = SQL_PATTERNS.ORDER_BY.test(text) ? `${text}, ${column} ${dir}` : `${text} ORDER BY ${column} ${dir}`;
15201
+ return makeSub(newText, params, hasWhere);
15202
+ },
15203
+ limit(n) {
15204
+ if (!Number.isInteger(n) || n < 0)
15205
+ throw new TypeError(`[query-builder] selectFromSub.limit(n): expected non-negative integer, got ${n}`);
15206
+ const newText = SQL_PATTERNS.LIMIT.test(text) ? text.replace(SQL_PATTERNS.LIMIT, ` LIMIT ${n}`) : `${text} LIMIT ${n}`;
15207
+ return makeSub(newText, params, hasWhere);
15208
+ },
15209
+ offset(n) {
15210
+ if (!Number.isInteger(n) || n < 0)
15211
+ throw new TypeError(`[query-builder] selectFromSub.offset(n): expected non-negative integer, got ${n}`);
15212
+ const newText = SQL_PATTERNS.OFFSET.test(text) ? text.replace(SQL_PATTERNS.OFFSET, ` OFFSET ${n}`) : `${text} OFFSET ${n}`;
15213
+ return makeSub(newText, params, hasWhere);
15214
+ },
15215
+ toSQL() {
15216
+ return makeExecutableQuery(build(), text);
15217
+ },
15218
+ async execute() {
15219
+ return runWithHooks(build(), "select");
15220
+ },
15221
+ async executeTakeFirst() {
15222
+ const rows = await runWithHooks(build(), "select");
15346
15223
  return Array.isArray(rows) ? rows[0] : rows;
15347
15224
  },
15348
- firstOrFail: async () => {
15349
- const rows = await runWithHooks(newQ, "select");
15225
+ async executeTakeFirstOrThrow() {
15226
+ const rows = await runWithHooks(build(), "select");
15227
+ const first = Array.isArray(rows) ? rows[0] : rows;
15228
+ if (!first)
15229
+ throw new Error("Record not found");
15230
+ return first;
15231
+ },
15232
+ async get() {
15233
+ return runWithHooks(build(), "select");
15234
+ },
15235
+ async first() {
15236
+ const rows = await runWithHooks(build(), "select");
15237
+ return Array.isArray(rows) ? rows[0] : rows;
15238
+ },
15239
+ async firstOrFail() {
15240
+ const rows = await runWithHooks(build(), "select");
15350
15241
  const first = Array.isArray(rows) ? rows[0] : rows;
15351
15242
  if (!first)
15352
15243
  throw new Error("No rows found");
15353
15244
  return first;
15354
15245
  },
15355
- exists: async () => {
15356
- const countQ = sql`SELECT EXISTS(${newQ}) as exists`;
15357
- const result = await runWithHooks(countQ, "select");
15358
- return result?.[0]?.exists === true;
15246
+ async count() {
15247
+ const q = params.length > 0 ? _sql.unsafe(`SELECT COUNT(*) as c FROM (${text}) as sub`, params) : _sql.unsafe(`SELECT COUNT(*) as c FROM (${text}) as sub`);
15248
+ const rows = await runWithHooks(q, "select");
15249
+ return Number(rows?.[0]?.c ?? 0);
15250
+ },
15251
+ async exists() {
15252
+ const q = params.length > 0 ? _sql.unsafe(`SELECT EXISTS(${text}) as e`, params) : _sql.unsafe(`SELECT EXISTS(${text}) as e`);
15253
+ const result = await runWithHooks(q, "select");
15254
+ return Boolean(result?.[0]?.e);
15359
15255
  },
15360
- doesntExist: async () => {
15361
- const exists = await base.exists();
15362
- return !exists;
15256
+ async doesntExist() {
15257
+ return !await base.exists();
15363
15258
  },
15364
- values: () => newQ.values(),
15365
- raw: () => newQ.raw(),
15366
- cancel: () => {
15259
+ values() {
15260
+ return build().values();
15261
+ },
15262
+ raw() {
15263
+ return build().raw();
15264
+ },
15265
+ cancel() {
15367
15266
  try {
15368
- newQ.cancel();
15267
+ build().cancel();
15369
15268
  } catch {}
15370
15269
  },
15371
- simple: () => newQ.simple(),
15372
- toText: () => String(newQ),
15373
- toParams: () => newQ.values?.() ?? []
15270
+ whereRaw: subqueryNotSupported("whereRaw"),
15271
+ whereColumn: subqueryNotSupported("whereColumn"),
15272
+ orWhereColumn: subqueryNotSupported("orWhereColumn"),
15273
+ whereIn: subqueryNotSupported("whereIn"),
15274
+ orWhereIn: subqueryNotSupported("orWhereIn"),
15275
+ whereNotIn: subqueryNotSupported("whereNotIn"),
15276
+ orWhereNotIn: subqueryNotSupported("orWhereNotIn"),
15277
+ whereLike: subqueryNotSupported("whereLike"),
15278
+ whereILike: subqueryNotSupported("whereILike"),
15279
+ orWhereLike: subqueryNotSupported("orWhereLike"),
15280
+ orWhereILike: subqueryNotSupported("orWhereILike"),
15281
+ whereNotLike: subqueryNotSupported("whereNotLike"),
15282
+ whereNotILike: subqueryNotSupported("whereNotILike"),
15283
+ orWhereNotLike: subqueryNotSupported("orWhereNotLike"),
15284
+ orWhereNotILike: subqueryNotSupported("orWhereNotILike"),
15285
+ whereAny: subqueryNotSupported("whereAny"),
15286
+ whereAll: subqueryNotSupported("whereAll"),
15287
+ whereNone: subqueryNotSupported("whereNone"),
15288
+ whereNested: subqueryNotSupported("whereNested"),
15289
+ orWhereNested: subqueryNotSupported("orWhereNested"),
15290
+ whereDate: subqueryNotSupported("whereDate"),
15291
+ whereBetween: subqueryNotSupported("whereBetween"),
15292
+ whereNotBetween: subqueryNotSupported("whereNotBetween"),
15293
+ whereJsonContains: subqueryNotSupported("whereJsonContains"),
15294
+ whereJsonPath: subqueryNotSupported("whereJsonPath"),
15295
+ whereNull: subqueryNotSupported("whereNull"),
15296
+ whereNotNull: subqueryNotSupported("whereNotNull"),
15297
+ whereExists: subqueryNotSupported("whereExists"),
15298
+ whereJsonDoesntContain: subqueryNotSupported("whereJsonDoesntContain"),
15299
+ whereJsonContainsKey: subqueryNotSupported("whereJsonContainsKey"),
15300
+ whereJsonDoesntContainKey: subqueryNotSupported("whereJsonDoesntContainKey"),
15301
+ whereJsonLength: subqueryNotSupported("whereJsonLength"),
15302
+ join: subqueryNotSupported("join"),
15303
+ joinSub: subqueryNotSupported("joinSub"),
15304
+ innerJoin: subqueryNotSupported("innerJoin"),
15305
+ leftJoin: subqueryNotSupported("leftJoin"),
15306
+ leftJoinSub: subqueryNotSupported("leftJoinSub"),
15307
+ rightJoin: subqueryNotSupported("rightJoin"),
15308
+ crossJoin: subqueryNotSupported("crossJoin"),
15309
+ crossJoinSub: subqueryNotSupported("crossJoinSub"),
15310
+ groupBy: subqueryNotSupported("groupBy"),
15311
+ groupByRaw: subqueryNotSupported("groupByRaw"),
15312
+ having: subqueryNotSupported("having"),
15313
+ havingRaw: subqueryNotSupported("havingRaw"),
15314
+ addSelect: subqueryNotSupported("addSelect"),
15315
+ select: subqueryNotSupported("select"),
15316
+ selectAll: subqueryNotSupported("selectAll"),
15317
+ orderByDesc: subqueryNotSupported("orderByDesc"),
15318
+ inRandomOrder: subqueryNotSupported("inRandomOrder"),
15319
+ reorder: subqueryNotSupported("reorder"),
15320
+ orderByRaw: subqueryNotSupported("orderByRaw"),
15321
+ union: subqueryNotSupported("union"),
15322
+ unionAll: subqueryNotSupported("unionAll"),
15323
+ forPage: subqueryNotSupported("forPage"),
15324
+ selectAllRelations: subqueryNotSupported("selectAllRelations"),
15325
+ with: subqueryNotSupported("with"),
15326
+ value: subqueryNotSupported("value"),
15327
+ pluck: subqueryNotSupported("pluck"),
15328
+ cursorPaginate: subqueryNotSupported("cursorPaginate"),
15329
+ paginate: subqueryNotSupported("paginate"),
15330
+ simplePaginate: subqueryNotSupported("simplePaginate"),
15331
+ chunk: subqueryNotSupported("chunk"),
15332
+ chunkById: subqueryNotSupported("chunkById"),
15333
+ eachById: subqueryNotSupported("eachById"),
15334
+ avg: subqueryNotSupported("avg"),
15335
+ sum: subqueryNotSupported("sum"),
15336
+ max: subqueryNotSupported("max"),
15337
+ min: subqueryNotSupported("min"),
15338
+ find: subqueryNotSupported("find"),
15339
+ findOrFail: subqueryNotSupported("findOrFail"),
15340
+ findMany: subqueryNotSupported("findMany"),
15341
+ latest: subqueryNotSupported("latest"),
15342
+ oldest: subqueryNotSupported("oldest"),
15343
+ lazy: subqueryNotSupported("lazy"),
15344
+ lazyById: subqueryNotSupported("lazyById"),
15345
+ pipe: (fn) => fn(base),
15346
+ when: subqueryNotSupported("when"),
15347
+ tap: () => base,
15348
+ dump: () => base,
15349
+ dd: () => {
15350
+ throw new Error("Dump and Die");
15351
+ },
15352
+ explain: () => Promise.resolve([]),
15353
+ simple: () => build().simple(),
15354
+ toText: () => text,
15355
+ toParams: () => [...params],
15356
+ withTimeout: () => base,
15357
+ abort: () => base,
15358
+ lockForUpdate: () => base,
15359
+ sharedLock: () => base,
15360
+ withCTE: () => base,
15361
+ withRecursive: () => base,
15362
+ cache: () => base,
15363
+ clone: () => base,
15364
+ withTrashed: () => base,
15365
+ onlyTrashed: () => base,
15366
+ scope: () => base,
15367
+ get rows() {
15368
+ return [];
15369
+ },
15370
+ get row() {
15371
+ return;
15372
+ }
15374
15373
  };
15374
+ return base;
15375
15375
  }
15376
- function applyWhereCondition(query, expr, op, value, prefix = "WHERE") {
15377
- if (typeof expr === "string" && op !== undefined) {
15378
- const clause = Array.isArray(value) ? `${expr} IN (?)` : `${expr} ${op} ?`;
15379
- return sql`${query} ${sql(prefix)} ${sql(clause)}`;
15380
- } else if (Array.isArray(expr) && expr.length === 3) {
15381
- const [column, operator, val] = expr;
15382
- const clause = Array.isArray(val) ? `${column} IN (?)` : `${column} ${operator} ?`;
15383
- return sql`${query} ${sql(prefix)} ${sql(clause)}`;
15384
- } else if (typeof expr === "object" && expr !== null) {
15385
- const conditions = Object.entries(expr).map(([key, val]) => {
15386
- const clause = Array.isArray(val) ? `${key} IN (?)` : `${key} = ?`;
15387
- return sql`${sql(clause)}`;
15388
- });
15389
- const combined = conditions.reduce((acc, cond, i) => i === 0 ? cond : sql`${acc} AND ${cond}`);
15390
- return sql`${query} ${sql(prefix)} ${combined}`;
15391
- }
15392
- return query;
15393
- }
15394
- return base;
15376
+ return makeSub(baseText, subParams);
15395
15377
  },
15396
15378
  insertInto(table) {
15397
15379
  let built;
@@ -24639,8 +24621,45 @@ function resolveRelation(definition, relationName) {
24639
24621
  pivotTimestamps: Boolean(config6?.pivot?.timestamps)
24640
24622
  };
24641
24623
  }
24624
+ const resolveThrough = (field, type) => {
24625
+ if (!field || typeof field !== "object" || Array.isArray(field))
24626
+ return null;
24627
+ const entry = field[relationName] ?? field[Object.keys(field).find((k2) => k2.toLowerCase() === relationName.toLowerCase()) ?? ""];
24628
+ if (!entry || typeof entry !== "object")
24629
+ return null;
24630
+ const throughModelName = entry.through;
24631
+ const targetModelName = entry.target;
24632
+ if (!throughModelName || !targetModelName)
24633
+ return null;
24634
+ const throughModel = getModelFromRegistry(throughModelName);
24635
+ const throughDef = throughModel?.getDefinition?.() || throughModel?.definition;
24636
+ const throughTable = throughModel?.getTable?.() || throughDef?.table || toTableName(throughModelName);
24637
+ const throughPk = throughDef?.primaryKey || "id";
24638
+ const targetModel = getModelFromRegistry(targetModelName);
24639
+ const targetTable = targetModel?.getTable?.() || toTableName(targetModelName);
24640
+ return {
24641
+ type,
24642
+ relatedModelName: targetModelName,
24643
+ relatedTable: targetTable,
24644
+ foreignKey: `${toSnakeCase(parentName)}_id`,
24645
+ localKey: parentPk,
24646
+ throughTable,
24647
+ throughForeignKey: `${toSnakeCase(parentName)}_id`,
24648
+ throughLocalKey: throughPk,
24649
+ targetForeignKey: `${singularizeWord(throughTable)}_id`
24650
+ };
24651
+ };
24652
+ const hmt = resolveThrough(definition.hasManyThrough, "hasManyThrough");
24653
+ if (hmt)
24654
+ return hmt;
24655
+ const hot = resolveThrough(definition.hasOneThrough, "hasOneThrough");
24656
+ if (hot)
24657
+ return hot;
24642
24658
  return null;
24643
24659
  }
24660
+ function singularizeWord(table) {
24661
+ return table.endsWith("s") ? table.slice(0, -1) : table;
24662
+ }
24644
24663
 
24645
24664
  class BelongsToManyRelationBuilder {
24646
24665
  _parent;
@@ -25277,6 +25296,40 @@ class ModelQueryBuilder {
25277
25296
  instance.setRelation(relationName, relatedInstances);
25278
25297
  }
25279
25298
  }
25299
+ if (rel.type === "hasManyThrough" || rel.type === "hasOneThrough") {
25300
+ const parentIds = instances.map((i2) => i2.get(pk)).filter((id) => id != null);
25301
+ if (parentIds.length === 0)
25302
+ continue;
25303
+ const throughPk = rel.throughLocalKey || "id";
25304
+ const throughPh = parentIds.map(() => "?").join(", ");
25305
+ const throughRows = await exec.all(`SELECT ${throughPk}, ${rel.throughForeignKey} FROM ${rel.throughTable} WHERE ${rel.throughForeignKey} IN (${throughPh})`, parentIds);
25306
+ if (throughRows.length === 0) {
25307
+ for (const instance of instances)
25308
+ instance.setRelation(relationName, rel.type === "hasManyThrough" ? [] : null);
25309
+ continue;
25310
+ }
25311
+ const throughToParent = new Map;
25312
+ for (const t2 of throughRows)
25313
+ throughToParent.set(t2[throughPk], t2[rel.throughForeignKey]);
25314
+ const throughIds = [...new Set(throughRows.map((t2) => t2[throughPk]))];
25315
+ const targetPh = throughIds.map(() => "?").join(", ");
25316
+ const targetRows = await exec.all(`SELECT * FROM ${rel.relatedTable} WHERE ${rel.targetForeignKey} IN (${targetPh})`, throughIds);
25317
+ const relatedModelDef = getModelFromRegistry(rel.relatedModelName);
25318
+ const relDef = relatedModelDef?.getDefinition?.() || relatedModelDef?.definition || this._definition;
25319
+ const byParent = new Map;
25320
+ for (const row of targetRows) {
25321
+ const parentVal = throughToParent.get(row[rel.targetForeignKey]);
25322
+ if (parentVal == null)
25323
+ continue;
25324
+ if (!byParent.has(parentVal))
25325
+ byParent.set(parentVal, []);
25326
+ byParent.get(parentVal).push(new ModelInstance(relDef, row));
25327
+ }
25328
+ for (const instance of instances) {
25329
+ const group = byParent.get(instance.get(pk)) || [];
25330
+ instance.setRelation(relationName, rel.type === "hasManyThrough" ? group : group[0] ?? null);
25331
+ }
25332
+ }
25280
25333
  }
25281
25334
  }
25282
25335
  async get() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-query-builder",
3
3
  "type": "module",
4
- "version": "0.1.35",
4
+ "version": "0.1.37",
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",