bun-query-builder 0.1.28 → 0.1.30
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 +144 -89
- package/dist/client.d.ts +25 -12
- package/dist/orm.d.ts +4 -4
- package/dist/src/index.js +143 -88
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -12391,6 +12391,61 @@ function validateIdentifier(name, context) {
|
|
|
12391
12391
|
throw new Error(`[query-builder] Invalid identifier${contextMsg}: '${name}'. Identifiers must start with a letter or underscore and contain only alphanumeric characters, underscores, and dots.`);
|
|
12392
12392
|
}
|
|
12393
12393
|
}
|
|
12394
|
+
function assertSafeWhereOperator(op, context) {
|
|
12395
|
+
if (typeof op !== "string")
|
|
12396
|
+
throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
|
|
12397
|
+
const lower = op.toLowerCase();
|
|
12398
|
+
if (!SAFE_WHERE_OPERATORS.has(lower))
|
|
12399
|
+
throw new TypeError(`[query-builder] ${context}: refusing to use '${op}' as a SQL operator \u2014 not in the allowed set (${[...SAFE_WHERE_OPERATORS].join(", ")})`);
|
|
12400
|
+
return op;
|
|
12401
|
+
}
|
|
12402
|
+
function validateQualifiedIdentifier(value, context) {
|
|
12403
|
+
if (typeof value !== "string" || value.length === 0)
|
|
12404
|
+
throw new TypeError(`[query-builder] ${context}: identifier must be a non-empty string, got ${typeof value}`);
|
|
12405
|
+
if (value.length > 129)
|
|
12406
|
+
throw new TypeError(`[query-builder] ${context}: identifier '${value}' too long`);
|
|
12407
|
+
const parts = value.split(".");
|
|
12408
|
+
if (parts.length > 2)
|
|
12409
|
+
throw new TypeError(`[query-builder] ${context}: identifier '${value}' has more than one dot \u2014 only \`table.column\` is allowed`);
|
|
12410
|
+
for (const part of parts) {
|
|
12411
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(part))
|
|
12412
|
+
throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
|
|
12413
|
+
}
|
|
12414
|
+
}
|
|
12415
|
+
function assertSqlFragment(fragment, context) {
|
|
12416
|
+
if (fragment === null || fragment === undefined) {
|
|
12417
|
+
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12418
|
+
}
|
|
12419
|
+
if (typeof fragment === "string") {
|
|
12420
|
+
warnOnceBareSqlFragment(context);
|
|
12421
|
+
}
|
|
12422
|
+
}
|
|
12423
|
+
function warnOnceBareSqlFragment(context) {
|
|
12424
|
+
if (warnedSqlFragmentContexts.has(context))
|
|
12425
|
+
return;
|
|
12426
|
+
warnedSqlFragmentContexts.add(context);
|
|
12427
|
+
console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer \`sql\`...\`\` tagged-template fragments so values are parameterised ` + `instead of concatenated \u2014 concatenating request input into SQL is an ` + `injection vector. This will become a hard error in a future release.`);
|
|
12428
|
+
}
|
|
12429
|
+
function formatSubqueryValue(val) {
|
|
12430
|
+
if (val === null)
|
|
12431
|
+
return "NULL";
|
|
12432
|
+
if (typeof val === "number" && Number.isFinite(val))
|
|
12433
|
+
return String(val);
|
|
12434
|
+
if (typeof val === "boolean")
|
|
12435
|
+
return val ? "1" : "0";
|
|
12436
|
+
if (typeof val === "string")
|
|
12437
|
+
return `'${val.replace(/'/g, "''")}'`;
|
|
12438
|
+
throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
|
|
12439
|
+
}
|
|
12440
|
+
function buildOverClause(partitionBy, orderBy) {
|
|
12441
|
+
const cols = Array.isArray(partitionBy) ? partitionBy : partitionBy ? [partitionBy] : [];
|
|
12442
|
+
const parts = [];
|
|
12443
|
+
if (cols.length)
|
|
12444
|
+
parts.push(`PARTITION BY ${cols.join(", ")}`);
|
|
12445
|
+
if (orderBy && orderBy.length)
|
|
12446
|
+
parts.push(`ORDER BY ${orderBy.map(([c, d]) => `${c} ${d === "desc" ? "DESC" : "ASC"}`).join(", ")}`);
|
|
12447
|
+
return parts.length ? `OVER (${parts.join(" ")})` : "OVER ()";
|
|
12448
|
+
}
|
|
12394
12449
|
|
|
12395
12450
|
class QueryCache {
|
|
12396
12451
|
cache = new Map;
|
|
@@ -12455,6 +12510,16 @@ function sleep(ms) {
|
|
|
12455
12510
|
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
12456
12511
|
}
|
|
12457
12512
|
function reorderSelectClauses(sql) {
|
|
12513
|
+
const hit = reorderCache.get(sql);
|
|
12514
|
+
if (hit !== undefined)
|
|
12515
|
+
return hit;
|
|
12516
|
+
const out = computeReorderedClauses(sql);
|
|
12517
|
+
if (reorderCache.size >= REORDER_CACHE_MAX)
|
|
12518
|
+
reorderCache.clear();
|
|
12519
|
+
reorderCache.set(sql, out);
|
|
12520
|
+
return out;
|
|
12521
|
+
}
|
|
12522
|
+
function computeReorderedClauses(sql) {
|
|
12458
12523
|
const KEYWORDS = [
|
|
12459
12524
|
{ key: "GROUP_BY", tokens: /^GROUP\s+BY\b/i },
|
|
12460
12525
|
{ key: "ORDER_BY", tokens: /^ORDER\s+BY\b/i },
|
|
@@ -12540,6 +12605,7 @@ function createQueryBuilder(state) {
|
|
|
12540
12605
|
const _sql = state?.sql ?? getOrCreateBunSql();
|
|
12541
12606
|
const meta = state?.meta;
|
|
12542
12607
|
const schema = state?.schema;
|
|
12608
|
+
const dynamicWhereColumnCache = new Map;
|
|
12543
12609
|
function applyCondition(expr) {
|
|
12544
12610
|
if (Array.isArray(expr)) {
|
|
12545
12611
|
const [col, op, val] = expr;
|
|
@@ -12858,65 +12924,9 @@ function createQueryBuilder(state) {
|
|
|
12858
12924
|
}
|
|
12859
12925
|
}
|
|
12860
12926
|
};
|
|
12861
|
-
const buildOverClause = (partitionBy, orderBy) => {
|
|
12862
|
-
const cols = Array.isArray(partitionBy) ? partitionBy : partitionBy ? [partitionBy] : [];
|
|
12863
|
-
const parts = [];
|
|
12864
|
-
if (cols.length)
|
|
12865
|
-
parts.push(`PARTITION BY ${cols.join(", ")}`);
|
|
12866
|
-
if (orderBy && orderBy.length)
|
|
12867
|
-
parts.push(`ORDER BY ${orderBy.map(([c, d]) => `${c} ${d === "desc" ? "DESC" : "ASC"}`).join(", ")}`);
|
|
12868
|
-
return parts.length ? `OVER (${parts.join(" ")})` : "OVER ()";
|
|
12869
|
-
};
|
|
12870
12927
|
const addWindowFunction = (fnExpr, alias, partitionBy, orderBy) => {
|
|
12871
12928
|
addToSelectClause(`${fnExpr} ${buildOverClause(partitionBy, orderBy)} AS ${alias}`);
|
|
12872
12929
|
};
|
|
12873
|
-
function assertSafeWhereOperator(op, context) {
|
|
12874
|
-
if (typeof op !== "string")
|
|
12875
|
-
throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
|
|
12876
|
-
const lower = op.toLowerCase();
|
|
12877
|
-
if (!SAFE_WHERE_OPERATORS.has(lower))
|
|
12878
|
-
throw new TypeError(`[query-builder] ${context}: refusing to use '${op}' as a SQL operator \u2014 not in the allowed set (${[...SAFE_WHERE_OPERATORS].join(", ")})`);
|
|
12879
|
-
return op;
|
|
12880
|
-
}
|
|
12881
|
-
function validateQualifiedIdentifier(value, context) {
|
|
12882
|
-
if (typeof value !== "string" || value.length === 0)
|
|
12883
|
-
throw new TypeError(`[query-builder] ${context}: identifier must be a non-empty string, got ${typeof value}`);
|
|
12884
|
-
if (value.length > 129)
|
|
12885
|
-
throw new TypeError(`[query-builder] ${context}: identifier '${value}' too long`);
|
|
12886
|
-
const parts = value.split(".");
|
|
12887
|
-
if (parts.length > 2)
|
|
12888
|
-
throw new TypeError(`[query-builder] ${context}: identifier '${value}' has more than one dot \u2014 only \`table.column\` is allowed`);
|
|
12889
|
-
for (const part of parts) {
|
|
12890
|
-
if (!/^[A-Z_][A-Z0-9_]*$/i.test(part))
|
|
12891
|
-
throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
|
|
12892
|
-
}
|
|
12893
|
-
}
|
|
12894
|
-
function assertSqlFragment(fragment, context) {
|
|
12895
|
-
if (fragment === null || fragment === undefined) {
|
|
12896
|
-
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12897
|
-
}
|
|
12898
|
-
if (typeof fragment === "string") {
|
|
12899
|
-
warnOnceBareSqlFragment(context);
|
|
12900
|
-
}
|
|
12901
|
-
}
|
|
12902
|
-
const warnedSqlFragmentContexts = new Set;
|
|
12903
|
-
function warnOnceBareSqlFragment(context) {
|
|
12904
|
-
if (warnedSqlFragmentContexts.has(context))
|
|
12905
|
-
return;
|
|
12906
|
-
warnedSqlFragmentContexts.add(context);
|
|
12907
|
-
console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer \`sql\`...\`\` tagged-template fragments so values are parameterised ` + `instead of concatenated \u2014 concatenating request input into SQL is an ` + `injection vector. This will become a hard error in a future release.`);
|
|
12908
|
-
}
|
|
12909
|
-
function formatSubqueryValue(val) {
|
|
12910
|
-
if (val === null)
|
|
12911
|
-
return "NULL";
|
|
12912
|
-
if (typeof val === "number" && Number.isFinite(val))
|
|
12913
|
-
return String(val);
|
|
12914
|
-
if (typeof val === "boolean")
|
|
12915
|
-
return val ? "1" : "0";
|
|
12916
|
-
if (typeof val === "string")
|
|
12917
|
-
return `'${val.replace(/'/g, "''")}'`;
|
|
12918
|
-
throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
|
|
12919
|
-
}
|
|
12920
12930
|
const buildHasSubquery = (parentTable, targetTable, pk, callback) => {
|
|
12921
12931
|
validateIdentifier(parentTable, "relationship subquery (parent table)");
|
|
12922
12932
|
validateIdentifier(targetTable, "relationship subquery (target table)");
|
|
@@ -12949,7 +12959,7 @@ function createQueryBuilder(state) {
|
|
|
12949
12959
|
const subQb = {
|
|
12950
12960
|
where: (col, op, val) => {
|
|
12951
12961
|
validateIdentifier(col, "relationship subquery condition");
|
|
12952
|
-
return `${targetTable}.${col} ${op
|
|
12962
|
+
return `${targetTable}.${col} ${assertSafeWhereOperator(op, "whereHas callback")} ${formatSubqueryValue(val)}`;
|
|
12953
12963
|
}
|
|
12954
12964
|
};
|
|
12955
12965
|
const condition = callback(subQb);
|
|
@@ -12978,7 +12988,7 @@ function createQueryBuilder(state) {
|
|
|
12978
12988
|
const subQb = {
|
|
12979
12989
|
where: (col, op, val) => {
|
|
12980
12990
|
validateIdentifier(col, "relationship subquery condition");
|
|
12981
|
-
return `${targetTable}.${col} ${op
|
|
12991
|
+
return `${targetTable}.${col} ${assertSafeWhereOperator(op, "whereHas callback")} ${formatSubqueryValue(val)}`;
|
|
12982
12992
|
}
|
|
12983
12993
|
};
|
|
12984
12994
|
const condition = callback(subQb);
|
|
@@ -14493,7 +14503,14 @@ function createQueryBuilder(state) {
|
|
|
14493
14503
|
return this;
|
|
14494
14504
|
},
|
|
14495
14505
|
toSQL() {
|
|
14496
|
-
|
|
14506
|
+
const sqlText = reorderSelectClauses(text);
|
|
14507
|
+
return {
|
|
14508
|
+
sql: sqlText,
|
|
14509
|
+
toString: () => sqlText,
|
|
14510
|
+
execute: () => ensureBuilt().execute(),
|
|
14511
|
+
values: () => ensureBuilt().values(),
|
|
14512
|
+
raw: () => ensureBuilt().raw()
|
|
14513
|
+
};
|
|
14497
14514
|
},
|
|
14498
14515
|
async value(column) {
|
|
14499
14516
|
const q = sql`${ensureBuilt()} LIMIT 1`;
|
|
@@ -14947,20 +14964,36 @@ function createQueryBuilder(state) {
|
|
|
14947
14964
|
if (typeof prop === "string" && (prop.startsWith("where") || prop.startsWith("orWhere") || prop.startsWith("andWhere"))) {
|
|
14948
14965
|
const isOr = prop.startsWith("orWhere");
|
|
14949
14966
|
const isAnd = prop.startsWith("andWhere");
|
|
14950
|
-
const
|
|
14951
|
-
|
|
14967
|
+
const cacheKey = `${String(table)}|${prop}`;
|
|
14968
|
+
let chosen = dynamicWhereColumnCache.get(cacheKey);
|
|
14969
|
+
if (chosen === undefined) {
|
|
14970
|
+
const raw = prop.replace(/^(?:or|and)?where/i, "");
|
|
14971
|
+
if (!raw) {
|
|
14972
|
+
dynamicWhereColumnCache.set(cacheKey, "");
|
|
14973
|
+
chosen = "";
|
|
14974
|
+
} else {
|
|
14975
|
+
const lowerFirst = raw.charAt(0).toLowerCase() + raw.slice(1);
|
|
14976
|
+
const snake = raw.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
14977
|
+
const available = schema ? Object.keys(schema[String(table)]?.columns ?? {}) : [];
|
|
14978
|
+
chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
|
|
14979
|
+
dynamicWhereColumnCache.set(cacheKey, chosen);
|
|
14980
|
+
}
|
|
14981
|
+
}
|
|
14982
|
+
if (chosen === "")
|
|
14952
14983
|
return () => receiver;
|
|
14953
|
-
const
|
|
14954
|
-
const toSnake = (s) => s.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
14955
|
-
const snake = toSnake(raw);
|
|
14956
|
-
const tbl = String(table);
|
|
14957
|
-
const available = schema ? Object.keys(schema[tbl]?.columns ?? {}) : [];
|
|
14958
|
-
const chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
|
|
14984
|
+
const column = chosen;
|
|
14959
14985
|
return (value) => {
|
|
14960
|
-
const expr = Array.isArray(value) ? sql`${sql(
|
|
14986
|
+
const expr = Array.isArray(value) ? sql`${sql(column)} IN ${sql(value)}` : sql`${sql(column)} = ${value}`;
|
|
14961
14987
|
built = isOr ? sql`${ensureBuilt()} OR ${expr}` : isAnd ? sql`${ensureBuilt()} AND ${expr}` : sql`${ensureBuilt()} WHERE ${expr}`;
|
|
14962
|
-
|
|
14963
|
-
|
|
14988
|
+
if (Array.isArray(value)) {
|
|
14989
|
+
const phs = getPlaceholders(value.length, whereParams.length + 1);
|
|
14990
|
+
addWhereText(isOr ? "OR" : isAnd ? "AND" : "WHERE", `${column} IN (${phs})`);
|
|
14991
|
+
whereParams.push(...value);
|
|
14992
|
+
} else {
|
|
14993
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
14994
|
+
addWhereText(isOr ? "OR" : isAnd ? "AND" : "WHERE", `${column} = ${ph}`);
|
|
14995
|
+
whereParams.push(value);
|
|
14996
|
+
}
|
|
14964
14997
|
return receiver;
|
|
14965
14998
|
};
|
|
14966
14999
|
}
|
|
@@ -16208,7 +16241,7 @@ function clearQueryCache() {
|
|
|
16208
16241
|
function setQueryCacheMaxSize(size) {
|
|
16209
16242
|
queryCache.setMaxSize(size);
|
|
16210
16243
|
}
|
|
16211
|
-
var SQL_PATTERNS, SAFE_WHERE_OPERATORS, queryCache;
|
|
16244
|
+
var SQL_PATTERNS, SAFE_WHERE_OPERATORS, warnedSqlFragmentContexts, queryCache, reorderCache, REORDER_CACHE_MAX = 500;
|
|
16212
16245
|
var init_client = __esm(() => {
|
|
16213
16246
|
init_config();
|
|
16214
16247
|
init_db();
|
|
@@ -16243,7 +16276,9 @@ var init_client = __esm(() => {
|
|
|
16243
16276
|
"between",
|
|
16244
16277
|
"not between"
|
|
16245
16278
|
]);
|
|
16279
|
+
warnedSqlFragmentContexts = new Set;
|
|
16246
16280
|
queryCache = new QueryCache;
|
|
16281
|
+
reorderCache = new Map;
|
|
16247
16282
|
});
|
|
16248
16283
|
|
|
16249
16284
|
// src/actions/cache.ts
|
|
@@ -23821,8 +23856,15 @@ function getModelFromRegistry(name) {
|
|
|
23821
23856
|
return _getModel(name);
|
|
23822
23857
|
}
|
|
23823
23858
|
function toPostgresPlaceholders(sql2) {
|
|
23859
|
+
const hit = pgPlaceholderCache.get(sql2);
|
|
23860
|
+
if (hit !== undefined)
|
|
23861
|
+
return hit;
|
|
23824
23862
|
let i2 = 0;
|
|
23825
|
-
|
|
23863
|
+
const out = sql2.replace(/\?/g, () => `$${++i2}`);
|
|
23864
|
+
if (pgPlaceholderCache.size >= PG_PLACEHOLDER_CACHE_MAX)
|
|
23865
|
+
pgPlaceholderCache.clear();
|
|
23866
|
+
pgPlaceholderCache.set(sql2, out);
|
|
23867
|
+
return out;
|
|
23826
23868
|
}
|
|
23827
23869
|
function extractChanges(res) {
|
|
23828
23870
|
if (res == null)
|
|
@@ -23954,21 +23996,25 @@ function timestampsEnabled(definition) {
|
|
|
23954
23996
|
return Boolean(t2?.useTimestamps || t2?.timestampable);
|
|
23955
23997
|
}
|
|
23956
23998
|
function collectBelongsToManyKeys(definition) {
|
|
23999
|
+
const cached = btmKeysCache.get(definition);
|
|
24000
|
+
if (cached)
|
|
24001
|
+
return cached;
|
|
23957
24002
|
const keys = new Set;
|
|
23958
24003
|
const rel = definition.belongsToMany;
|
|
23959
|
-
if (
|
|
23960
|
-
|
|
23961
|
-
|
|
23962
|
-
|
|
23963
|
-
|
|
23964
|
-
|
|
23965
|
-
|
|
23966
|
-
|
|
23967
|
-
}
|
|
23968
|
-
|
|
23969
|
-
|
|
23970
|
-
|
|
24004
|
+
if (rel) {
|
|
24005
|
+
if (Array.isArray(rel)) {
|
|
24006
|
+
for (const item of rel) {
|
|
24007
|
+
if (typeof item === "string")
|
|
24008
|
+
keys.add(item.toLowerCase());
|
|
24009
|
+
else if (item && typeof item === "object" && item.model)
|
|
24010
|
+
keys.add(item.model.toLowerCase());
|
|
24011
|
+
}
|
|
24012
|
+
} else if (typeof rel === "object") {
|
|
24013
|
+
for (const k2 of Object.keys(rel))
|
|
24014
|
+
keys.add(k2);
|
|
24015
|
+
}
|
|
23971
24016
|
}
|
|
24017
|
+
btmKeysCache.set(definition, keys);
|
|
23972
24018
|
return keys;
|
|
23973
24019
|
}
|
|
23974
24020
|
|
|
@@ -25203,7 +25249,14 @@ class ModelQueryBuilder {
|
|
|
25203
25249
|
sql2 += ` WHERE ${whereBody}`;
|
|
25204
25250
|
}
|
|
25205
25251
|
const row = await exec.get(sql2, params);
|
|
25206
|
-
|
|
25252
|
+
const v2 = row?.v;
|
|
25253
|
+
if (v2 == null)
|
|
25254
|
+
return null;
|
|
25255
|
+
if (typeof v2 === "string") {
|
|
25256
|
+
const n2 = Number(v2);
|
|
25257
|
+
return v2.trim() !== "" && !Number.isNaN(n2) ? n2 : v2;
|
|
25258
|
+
}
|
|
25259
|
+
return v2;
|
|
25207
25260
|
}
|
|
25208
25261
|
max(column) {
|
|
25209
25262
|
return this.aggregate("MAX", column);
|
|
@@ -25212,10 +25265,10 @@ class ModelQueryBuilder {
|
|
|
25212
25265
|
return this.aggregate("MIN", column);
|
|
25213
25266
|
}
|
|
25214
25267
|
async avg(column) {
|
|
25215
|
-
return await this.aggregate("AVG", column) || 0;
|
|
25268
|
+
return Number(await this.aggregate("AVG", column) ?? 0) || 0;
|
|
25216
25269
|
}
|
|
25217
25270
|
async sum(column) {
|
|
25218
|
-
return await this.aggregate("SUM", column) || 0;
|
|
25271
|
+
return Number(await this.aggregate("SUM", column) ?? 0) || 0;
|
|
25219
25272
|
}
|
|
25220
25273
|
async delete() {
|
|
25221
25274
|
const exec = getExecutor();
|
|
@@ -25473,11 +25526,13 @@ async function seedModel(definition, count, faker) {
|
|
|
25473
25526
|
await exec.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
|
|
25474
25527
|
}
|
|
25475
25528
|
}
|
|
25476
|
-
var SAFE_SQL_IDENTIFIER, _getModel = null, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000;
|
|
25529
|
+
var SAFE_SQL_IDENTIFIER, _getModel = null, pgPlaceholderCache, PG_PLACEHOLDER_CACHE_MAX = 500, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", btmKeysCache, snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000;
|
|
25477
25530
|
var init_orm = __esm(() => {
|
|
25478
25531
|
init_config();
|
|
25479
25532
|
init_db();
|
|
25480
25533
|
SAFE_SQL_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/i;
|
|
25534
|
+
pgPlaceholderCache = new Map;
|
|
25535
|
+
btmKeysCache = new WeakMap;
|
|
25481
25536
|
snakeCaseCache = new Map;
|
|
25482
25537
|
tableNameCache = new Map;
|
|
25483
25538
|
relationCache = new Map;
|
|
@@ -29929,7 +29984,7 @@ function getPrefix() {
|
|
|
29929
29984
|
}
|
|
29930
29985
|
var prefix = getPrefix();
|
|
29931
29986
|
// package.json
|
|
29932
|
-
var version2 = "0.1.
|
|
29987
|
+
var version2 = "0.1.30";
|
|
29933
29988
|
|
|
29934
29989
|
// bin/cli.ts
|
|
29935
29990
|
init_actions();
|
package/dist/client.d.ts
CHANGED
|
@@ -104,8 +104,11 @@ export declare interface BaseSelectQueryBuilder<DB extends DatabaseSchema<any>,
|
|
|
104
104
|
having: (expr: WhereExpression<any>) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
105
105
|
havingRaw: (fragment: SqlFragment) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
106
106
|
addSelect: (...columns: ((keyof DB[TTable]['columns'] & string) | string | SqlFragment)[]) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
107
|
-
select?:
|
|
108
|
-
|
|
107
|
+
select?: {
|
|
108
|
+
<K extends keyof DB[TTable]['columns'] & string>(columns: K[]): SelectQueryBuilder<DB, TTable, Pick<DB[TTable]['columns'], K>, TJoined>
|
|
109
|
+
(columns: string | SqlFragment | (string | SqlFragment)[]): SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
110
|
+
}
|
|
111
|
+
selectAll?: () => SelectQueryBuilder<DB, TTable, DB[TTable]['columns'], TJoined>
|
|
109
112
|
orderByRaw: (fragment: SqlFragment) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
110
113
|
union: (other: { toSQL: () => any }) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
111
114
|
unionAll: (other: { toSQL: () => any }) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
@@ -146,7 +149,7 @@ export declare interface BaseSelectQueryBuilder<DB extends DatabaseSchema<any>,
|
|
|
146
149
|
}
|
|
147
150
|
exists: () => Promise<boolean>
|
|
148
151
|
doesntExist: () => Promise<boolean>
|
|
149
|
-
cursorPaginate: (perPage: number, cursor?: string | number, column?: string, direction?: 'asc' | 'desc') => Promise<{ data:
|
|
152
|
+
cursorPaginate: (perPage: number, cursor?: string | number, column?: string, direction?: 'asc' | 'desc') => Promise<{ data: SelectedRow<DB, TTable, TSelected>[], meta: { perPage: number, nextCursor: string | number | null } }>
|
|
150
153
|
chunk: (size: number, handler: (rows: any[]) => Promise<void> | void) => Promise<void>
|
|
151
154
|
chunkById: (size: number, column?: string, handler?: (rows: any[]) => Promise<void> | void) => Promise<void>
|
|
152
155
|
eachById: (size: number, column?: string, handler?: (row: any) => Promise<void> | void) => Promise<void>
|
|
@@ -175,8 +178,8 @@ export declare interface BaseSelectQueryBuilder<DB extends DatabaseSchema<any>,
|
|
|
175
178
|
count: () => Promise<number>
|
|
176
179
|
avg: (column: keyof DB[TTable]['columns'] & string) => Promise<number>
|
|
177
180
|
sum: (column: keyof DB[TTable]['columns'] & string) => Promise<number>
|
|
178
|
-
max:
|
|
179
|
-
min:
|
|
181
|
+
max: <K extends keyof DB[TTable]['columns'] & string>(column: K) => Promise<DB[TTable]['columns'][K] | null>
|
|
182
|
+
min: <K extends keyof DB[TTable]['columns'] & string>(column: K) => Promise<DB[TTable]['columns'][K] | null>
|
|
180
183
|
readonly rows: TSelected[]
|
|
181
184
|
readonly row: TSelected
|
|
182
185
|
values: () => Promise<any[][]>
|
|
@@ -224,13 +227,19 @@ export declare interface TableQueryBuilder<DB extends DatabaseSchema<any>, TTabl
|
|
|
224
227
|
insert: (data: Partial<DB[TTable]['columns']> | Partial<DB[TTable]['columns']>[]) => InsertQueryBuilder<DB, TTable>
|
|
225
228
|
update: (values: Partial<DB[TTable]['columns']>) => UpdateQueryBuilder<DB, TTable>
|
|
226
229
|
delete: () => DeleteQueryBuilder<DB, TTable>
|
|
227
|
-
select:
|
|
230
|
+
select: <K extends keyof DB[TTable]['columns'] & string>(...columns: K[]) => SelectQueryBuilder<DB, TTable, Pick<DB[TTable]['columns'], K>>
|
|
228
231
|
}
|
|
229
232
|
export declare interface QueryBuilder<DB extends DatabaseSchema<any>> {
|
|
230
|
-
select:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
233
|
+
select: {
|
|
234
|
+
<TTable extends keyof DB & string, K extends keyof DB[TTable]['columns'] & string>(
|
|
235
|
+
table: TTable,
|
|
236
|
+
...columns: K[]
|
|
237
|
+
): SelectQueryBuilder<DB, TTable, Pick<DB[TTable]['columns'], K>>
|
|
238
|
+
<TTable extends keyof DB & string>(
|
|
239
|
+
table: TTable,
|
|
240
|
+
...columns: ((keyof DB[TTable]['columns'] & string) | `${string} as ${string}`)[]
|
|
241
|
+
): SelectQueryBuilder<DB, TTable, any>
|
|
242
|
+
}
|
|
234
243
|
selectFrom: <TTable extends keyof DB & string>(table: TTable) => TypedSelectQueryBuilder<DB, TTable, DB[TTable]['columns'], TTable, `SELECT * FROM ${TTable}`>
|
|
235
244
|
insertInto: <TTable extends keyof DB & string>(table: TTable) => TypedInsertQueryBuilder<DB, TTable>
|
|
236
245
|
updateTable: <TTable extends keyof DB & string>(table: TTable) => UpdateQueryBuilder<DB, TTable>
|
|
@@ -456,11 +465,15 @@ declare type _TypedDynamicWhereMethods<DB extends DatabaseSchema<any>, TTable ex
|
|
|
456
465
|
value: DB[TTable]['columns'][K],
|
|
457
466
|
) => TypedSelectQueryBuilder<DB, TTable, TSelected, TJoined, `${TSql} AND ${K} = ?`>
|
|
458
467
|
}
|
|
468
|
+
// NOTE: TypedSelectQueryBuilder must NOT also intersect DynamicWhereMethods —
|
|
469
|
+
// _TypedDynamicWhereMethods declares the same `where<Column>` keys, and the
|
|
470
|
+
// untyped variant (returning a plain SelectQueryBuilder) would win overload
|
|
471
|
+
// resolution, silently downgrading `toSQL()` from the composed literal SQL
|
|
472
|
+
// type back to `string` after any dynamic-where call.
|
|
459
473
|
export type TypedSelectQueryBuilder<DB extends DatabaseSchema<any>, TTable extends keyof DB & string, TSelected, TJoined extends string = TTable, TSql extends string = `SELECT * FROM ${TTable}`,> = Omit<
|
|
460
474
|
BaseSelectQueryBuilder<DB, TTable, TSelected, TJoined>,
|
|
461
475
|
'toSQL' | 'where' | 'andWhere' | 'orWhere' | 'orderBy' | 'limit'
|
|
462
|
-
> &
|
|
463
|
-
& _TypedDynamicWhereMethods<DB, TTable, TSelected, TJoined, TSql>
|
|
476
|
+
> & _TypedDynamicWhereMethods<DB, TTable, TSelected, TJoined, TSql>
|
|
464
477
|
& {
|
|
465
478
|
toSQL: () => TSql
|
|
466
479
|
where: (<K extends keyof DB[TTable]['columns'] & string>(
|
package/dist/orm.d.ts
CHANGED
|
@@ -317,8 +317,8 @@ declare class ModelInstance<TDef extends ModelDefinition, TSelected extends Colu
|
|
|
317
317
|
getAttribute<K extends TSelected>(key: K): K extends keyof ModelAttributes<TDef> ? ModelAttributes<TDef>[K] : unknown;
|
|
318
318
|
getAttributes(): Pick<ModelAttributes<TDef>, TSelected & keyof ModelAttributes<TDef>>;
|
|
319
319
|
set<K extends ColumnName<TDef>>(key: K, value: K extends keyof ModelAttributes<TDef> ? ModelAttributes<TDef>[K] : unknown): void;
|
|
320
|
-
only<K extends TSelected>(keys: ReadonlyArray<K>):
|
|
321
|
-
except<K extends TSelected>(keys: ReadonlyArray<K>):
|
|
320
|
+
only<K extends TSelected>(keys: ReadonlyArray<K>): Pick<ModelAttributes<TDef>, K & keyof ModelAttributes<TDef>>;
|
|
321
|
+
except<K extends TSelected>(keys: ReadonlyArray<K>): Omit<Pick<ModelAttributes<TDef>, TSelected & keyof ModelAttributes<TDef>>, K>;
|
|
322
322
|
getRelation<R extends InferRelationNames<TDef> & string>(name: R): LoadedRelationValue<TDef, R>;
|
|
323
323
|
setRelation(name: string, data: ModelInstance<any, any>[] | ModelInstance<any, any> | null): void;
|
|
324
324
|
getLoadedRelations(): Record<string, ModelInstance<any, any>[] | ModelInstance<any, any> | null>;
|
|
@@ -440,8 +440,8 @@ declare class ModelQueryBuilder<TDef extends ModelDefinition, TSelected extends
|
|
|
440
440
|
to: number | null
|
|
441
441
|
}>;
|
|
442
442
|
pluck<K extends ColumnName<TDef>>(column: K): Promise<(K extends keyof ModelAttributes<TDef> ? ModelAttributes<TDef>[K] : unknown)[]>;
|
|
443
|
-
max<K extends ColumnName<TDef>>(column: K): Promise<number | null>;
|
|
444
|
-
min<K extends ColumnName<TDef>>(column: K): Promise<number | null>;
|
|
443
|
+
max<K extends ColumnName<TDef>>(column: K): Promise<(K extends keyof ModelAttributes<TDef> ? ModelAttributes<TDef>[K] : number) | null>;
|
|
444
|
+
min<K extends ColumnName<TDef>>(column: K): Promise<(K extends keyof ModelAttributes<TDef> ? ModelAttributes<TDef>[K] : number) | null>;
|
|
445
445
|
avg<K extends NumericColumns<TDef>>(column: K): Promise<number>;
|
|
446
446
|
sum<K extends NumericColumns<TDef>>(column: K): Promise<number>;
|
|
447
447
|
delete(): Promise<number>;
|
package/dist/src/index.js
CHANGED
|
@@ -12391,6 +12391,61 @@ function validateIdentifier(name, context) {
|
|
|
12391
12391
|
throw new Error(`[query-builder] Invalid identifier${contextMsg}: '${name}'. Identifiers must start with a letter or underscore and contain only alphanumeric characters, underscores, and dots.`);
|
|
12392
12392
|
}
|
|
12393
12393
|
}
|
|
12394
|
+
function assertSafeWhereOperator(op, context) {
|
|
12395
|
+
if (typeof op !== "string")
|
|
12396
|
+
throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
|
|
12397
|
+
const lower = op.toLowerCase();
|
|
12398
|
+
if (!SAFE_WHERE_OPERATORS.has(lower))
|
|
12399
|
+
throw new TypeError(`[query-builder] ${context}: refusing to use '${op}' as a SQL operator \u2014 not in the allowed set (${[...SAFE_WHERE_OPERATORS].join(", ")})`);
|
|
12400
|
+
return op;
|
|
12401
|
+
}
|
|
12402
|
+
function validateQualifiedIdentifier(value, context) {
|
|
12403
|
+
if (typeof value !== "string" || value.length === 0)
|
|
12404
|
+
throw new TypeError(`[query-builder] ${context}: identifier must be a non-empty string, got ${typeof value}`);
|
|
12405
|
+
if (value.length > 129)
|
|
12406
|
+
throw new TypeError(`[query-builder] ${context}: identifier '${value}' too long`);
|
|
12407
|
+
const parts = value.split(".");
|
|
12408
|
+
if (parts.length > 2)
|
|
12409
|
+
throw new TypeError(`[query-builder] ${context}: identifier '${value}' has more than one dot \u2014 only \`table.column\` is allowed`);
|
|
12410
|
+
for (const part of parts) {
|
|
12411
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(part))
|
|
12412
|
+
throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
|
|
12413
|
+
}
|
|
12414
|
+
}
|
|
12415
|
+
function assertSqlFragment(fragment, context) {
|
|
12416
|
+
if (fragment === null || fragment === undefined) {
|
|
12417
|
+
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12418
|
+
}
|
|
12419
|
+
if (typeof fragment === "string") {
|
|
12420
|
+
warnOnceBareSqlFragment(context);
|
|
12421
|
+
}
|
|
12422
|
+
}
|
|
12423
|
+
function warnOnceBareSqlFragment(context) {
|
|
12424
|
+
if (warnedSqlFragmentContexts.has(context))
|
|
12425
|
+
return;
|
|
12426
|
+
warnedSqlFragmentContexts.add(context);
|
|
12427
|
+
console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer \`sql\`...\`\` tagged-template fragments so values are parameterised ` + `instead of concatenated \u2014 concatenating request input into SQL is an ` + `injection vector. This will become a hard error in a future release.`);
|
|
12428
|
+
}
|
|
12429
|
+
function formatSubqueryValue(val) {
|
|
12430
|
+
if (val === null)
|
|
12431
|
+
return "NULL";
|
|
12432
|
+
if (typeof val === "number" && Number.isFinite(val))
|
|
12433
|
+
return String(val);
|
|
12434
|
+
if (typeof val === "boolean")
|
|
12435
|
+
return val ? "1" : "0";
|
|
12436
|
+
if (typeof val === "string")
|
|
12437
|
+
return `'${val.replace(/'/g, "''")}'`;
|
|
12438
|
+
throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
|
|
12439
|
+
}
|
|
12440
|
+
function buildOverClause(partitionBy, orderBy) {
|
|
12441
|
+
const cols = Array.isArray(partitionBy) ? partitionBy : partitionBy ? [partitionBy] : [];
|
|
12442
|
+
const parts = [];
|
|
12443
|
+
if (cols.length)
|
|
12444
|
+
parts.push(`PARTITION BY ${cols.join(", ")}`);
|
|
12445
|
+
if (orderBy && orderBy.length)
|
|
12446
|
+
parts.push(`ORDER BY ${orderBy.map(([c, d]) => `${c} ${d === "desc" ? "DESC" : "ASC"}`).join(", ")}`);
|
|
12447
|
+
return parts.length ? `OVER (${parts.join(" ")})` : "OVER ()";
|
|
12448
|
+
}
|
|
12394
12449
|
|
|
12395
12450
|
class QueryCache {
|
|
12396
12451
|
cache = new Map;
|
|
@@ -12455,6 +12510,16 @@ function sleep(ms) {
|
|
|
12455
12510
|
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
12456
12511
|
}
|
|
12457
12512
|
function reorderSelectClauses(sql) {
|
|
12513
|
+
const hit = reorderCache.get(sql);
|
|
12514
|
+
if (hit !== undefined)
|
|
12515
|
+
return hit;
|
|
12516
|
+
const out = computeReorderedClauses(sql);
|
|
12517
|
+
if (reorderCache.size >= REORDER_CACHE_MAX)
|
|
12518
|
+
reorderCache.clear();
|
|
12519
|
+
reorderCache.set(sql, out);
|
|
12520
|
+
return out;
|
|
12521
|
+
}
|
|
12522
|
+
function computeReorderedClauses(sql) {
|
|
12458
12523
|
const KEYWORDS = [
|
|
12459
12524
|
{ key: "GROUP_BY", tokens: /^GROUP\s+BY\b/i },
|
|
12460
12525
|
{ key: "ORDER_BY", tokens: /^ORDER\s+BY\b/i },
|
|
@@ -12540,6 +12605,7 @@ function createQueryBuilder(state) {
|
|
|
12540
12605
|
const _sql = state?.sql ?? getOrCreateBunSql();
|
|
12541
12606
|
const meta = state?.meta;
|
|
12542
12607
|
const schema = state?.schema;
|
|
12608
|
+
const dynamicWhereColumnCache = new Map;
|
|
12543
12609
|
function applyCondition(expr) {
|
|
12544
12610
|
if (Array.isArray(expr)) {
|
|
12545
12611
|
const [col, op, val] = expr;
|
|
@@ -12858,65 +12924,9 @@ function createQueryBuilder(state) {
|
|
|
12858
12924
|
}
|
|
12859
12925
|
}
|
|
12860
12926
|
};
|
|
12861
|
-
const buildOverClause = (partitionBy, orderBy) => {
|
|
12862
|
-
const cols = Array.isArray(partitionBy) ? partitionBy : partitionBy ? [partitionBy] : [];
|
|
12863
|
-
const parts = [];
|
|
12864
|
-
if (cols.length)
|
|
12865
|
-
parts.push(`PARTITION BY ${cols.join(", ")}`);
|
|
12866
|
-
if (orderBy && orderBy.length)
|
|
12867
|
-
parts.push(`ORDER BY ${orderBy.map(([c, d]) => `${c} ${d === "desc" ? "DESC" : "ASC"}`).join(", ")}`);
|
|
12868
|
-
return parts.length ? `OVER (${parts.join(" ")})` : "OVER ()";
|
|
12869
|
-
};
|
|
12870
12927
|
const addWindowFunction = (fnExpr, alias, partitionBy, orderBy) => {
|
|
12871
12928
|
addToSelectClause(`${fnExpr} ${buildOverClause(partitionBy, orderBy)} AS ${alias}`);
|
|
12872
12929
|
};
|
|
12873
|
-
function assertSafeWhereOperator(op, context) {
|
|
12874
|
-
if (typeof op !== "string")
|
|
12875
|
-
throw new TypeError(`[query-builder] ${context}: operator must be a string, got ${typeof op}`);
|
|
12876
|
-
const lower = op.toLowerCase();
|
|
12877
|
-
if (!SAFE_WHERE_OPERATORS.has(lower))
|
|
12878
|
-
throw new TypeError(`[query-builder] ${context}: refusing to use '${op}' as a SQL operator \u2014 not in the allowed set (${[...SAFE_WHERE_OPERATORS].join(", ")})`);
|
|
12879
|
-
return op;
|
|
12880
|
-
}
|
|
12881
|
-
function validateQualifiedIdentifier(value, context) {
|
|
12882
|
-
if (typeof value !== "string" || value.length === 0)
|
|
12883
|
-
throw new TypeError(`[query-builder] ${context}: identifier must be a non-empty string, got ${typeof value}`);
|
|
12884
|
-
if (value.length > 129)
|
|
12885
|
-
throw new TypeError(`[query-builder] ${context}: identifier '${value}' too long`);
|
|
12886
|
-
const parts = value.split(".");
|
|
12887
|
-
if (parts.length > 2)
|
|
12888
|
-
throw new TypeError(`[query-builder] ${context}: identifier '${value}' has more than one dot \u2014 only \`table.column\` is allowed`);
|
|
12889
|
-
for (const part of parts) {
|
|
12890
|
-
if (!/^[A-Z_][A-Z0-9_]*$/i.test(part))
|
|
12891
|
-
throw new TypeError(`[query-builder] ${context}: identifier segment '${part}' contains characters outside [A-Za-z0-9_]`);
|
|
12892
|
-
}
|
|
12893
|
-
}
|
|
12894
|
-
function assertSqlFragment(fragment, context) {
|
|
12895
|
-
if (fragment === null || fragment === undefined) {
|
|
12896
|
-
throw new TypeError(`[query-builder] ${context}: fragment must be a SqlFragment, got ${fragment}`);
|
|
12897
|
-
}
|
|
12898
|
-
if (typeof fragment === "string") {
|
|
12899
|
-
warnOnceBareSqlFragment(context);
|
|
12900
|
-
}
|
|
12901
|
-
}
|
|
12902
|
-
const warnedSqlFragmentContexts = new Set;
|
|
12903
|
-
function warnOnceBareSqlFragment(context) {
|
|
12904
|
-
if (warnedSqlFragmentContexts.has(context))
|
|
12905
|
-
return;
|
|
12906
|
-
warnedSqlFragmentContexts.add(context);
|
|
12907
|
-
console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer \`sql\`...\`\` tagged-template fragments so values are parameterised ` + `instead of concatenated \u2014 concatenating request input into SQL is an ` + `injection vector. This will become a hard error in a future release.`);
|
|
12908
|
-
}
|
|
12909
|
-
function formatSubqueryValue(val) {
|
|
12910
|
-
if (val === null)
|
|
12911
|
-
return "NULL";
|
|
12912
|
-
if (typeof val === "number" && Number.isFinite(val))
|
|
12913
|
-
return String(val);
|
|
12914
|
-
if (typeof val === "boolean")
|
|
12915
|
-
return val ? "1" : "0";
|
|
12916
|
-
if (typeof val === "string")
|
|
12917
|
-
return `'${val.replace(/'/g, "''")}'`;
|
|
12918
|
-
throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
|
|
12919
|
-
}
|
|
12920
12930
|
const buildHasSubquery = (parentTable, targetTable, pk, callback) => {
|
|
12921
12931
|
validateIdentifier(parentTable, "relationship subquery (parent table)");
|
|
12922
12932
|
validateIdentifier(targetTable, "relationship subquery (target table)");
|
|
@@ -12949,7 +12959,7 @@ function createQueryBuilder(state) {
|
|
|
12949
12959
|
const subQb = {
|
|
12950
12960
|
where: (col, op, val) => {
|
|
12951
12961
|
validateIdentifier(col, "relationship subquery condition");
|
|
12952
|
-
return `${targetTable}.${col} ${op
|
|
12962
|
+
return `${targetTable}.${col} ${assertSafeWhereOperator(op, "whereHas callback")} ${formatSubqueryValue(val)}`;
|
|
12953
12963
|
}
|
|
12954
12964
|
};
|
|
12955
12965
|
const condition = callback(subQb);
|
|
@@ -12978,7 +12988,7 @@ function createQueryBuilder(state) {
|
|
|
12978
12988
|
const subQb = {
|
|
12979
12989
|
where: (col, op, val) => {
|
|
12980
12990
|
validateIdentifier(col, "relationship subquery condition");
|
|
12981
|
-
return `${targetTable}.${col} ${op
|
|
12991
|
+
return `${targetTable}.${col} ${assertSafeWhereOperator(op, "whereHas callback")} ${formatSubqueryValue(val)}`;
|
|
12982
12992
|
}
|
|
12983
12993
|
};
|
|
12984
12994
|
const condition = callback(subQb);
|
|
@@ -14493,7 +14503,14 @@ function createQueryBuilder(state) {
|
|
|
14493
14503
|
return this;
|
|
14494
14504
|
},
|
|
14495
14505
|
toSQL() {
|
|
14496
|
-
|
|
14506
|
+
const sqlText = reorderSelectClauses(text);
|
|
14507
|
+
return {
|
|
14508
|
+
sql: sqlText,
|
|
14509
|
+
toString: () => sqlText,
|
|
14510
|
+
execute: () => ensureBuilt().execute(),
|
|
14511
|
+
values: () => ensureBuilt().values(),
|
|
14512
|
+
raw: () => ensureBuilt().raw()
|
|
14513
|
+
};
|
|
14497
14514
|
},
|
|
14498
14515
|
async value(column) {
|
|
14499
14516
|
const q = sql`${ensureBuilt()} LIMIT 1`;
|
|
@@ -14947,20 +14964,36 @@ function createQueryBuilder(state) {
|
|
|
14947
14964
|
if (typeof prop === "string" && (prop.startsWith("where") || prop.startsWith("orWhere") || prop.startsWith("andWhere"))) {
|
|
14948
14965
|
const isOr = prop.startsWith("orWhere");
|
|
14949
14966
|
const isAnd = prop.startsWith("andWhere");
|
|
14950
|
-
const
|
|
14951
|
-
|
|
14967
|
+
const cacheKey = `${String(table)}|${prop}`;
|
|
14968
|
+
let chosen = dynamicWhereColumnCache.get(cacheKey);
|
|
14969
|
+
if (chosen === undefined) {
|
|
14970
|
+
const raw = prop.replace(/^(?:or|and)?where/i, "");
|
|
14971
|
+
if (!raw) {
|
|
14972
|
+
dynamicWhereColumnCache.set(cacheKey, "");
|
|
14973
|
+
chosen = "";
|
|
14974
|
+
} else {
|
|
14975
|
+
const lowerFirst = raw.charAt(0).toLowerCase() + raw.slice(1);
|
|
14976
|
+
const snake = raw.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
14977
|
+
const available = schema ? Object.keys(schema[String(table)]?.columns ?? {}) : [];
|
|
14978
|
+
chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
|
|
14979
|
+
dynamicWhereColumnCache.set(cacheKey, chosen);
|
|
14980
|
+
}
|
|
14981
|
+
}
|
|
14982
|
+
if (chosen === "")
|
|
14952
14983
|
return () => receiver;
|
|
14953
|
-
const
|
|
14954
|
-
const toSnake = (s) => s.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
14955
|
-
const snake = toSnake(raw);
|
|
14956
|
-
const tbl = String(table);
|
|
14957
|
-
const available = schema ? Object.keys(schema[tbl]?.columns ?? {}) : [];
|
|
14958
|
-
const chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
|
|
14984
|
+
const column = chosen;
|
|
14959
14985
|
return (value) => {
|
|
14960
|
-
const expr = Array.isArray(value) ? sql`${sql(
|
|
14986
|
+
const expr = Array.isArray(value) ? sql`${sql(column)} IN ${sql(value)}` : sql`${sql(column)} = ${value}`;
|
|
14961
14987
|
built = isOr ? sql`${ensureBuilt()} OR ${expr}` : isAnd ? sql`${ensureBuilt()} AND ${expr}` : sql`${ensureBuilt()} WHERE ${expr}`;
|
|
14962
|
-
|
|
14963
|
-
|
|
14988
|
+
if (Array.isArray(value)) {
|
|
14989
|
+
const phs = getPlaceholders(value.length, whereParams.length + 1);
|
|
14990
|
+
addWhereText(isOr ? "OR" : isAnd ? "AND" : "WHERE", `${column} IN (${phs})`);
|
|
14991
|
+
whereParams.push(...value);
|
|
14992
|
+
} else {
|
|
14993
|
+
const ph = getPlaceholder(whereParams.length + 1);
|
|
14994
|
+
addWhereText(isOr ? "OR" : isAnd ? "AND" : "WHERE", `${column} = ${ph}`);
|
|
14995
|
+
whereParams.push(value);
|
|
14996
|
+
}
|
|
14964
14997
|
return receiver;
|
|
14965
14998
|
};
|
|
14966
14999
|
}
|
|
@@ -16208,7 +16241,7 @@ function clearQueryCache() {
|
|
|
16208
16241
|
function setQueryCacheMaxSize(size) {
|
|
16209
16242
|
queryCache.setMaxSize(size);
|
|
16210
16243
|
}
|
|
16211
|
-
var SQL_PATTERNS, SAFE_WHERE_OPERATORS, queryCache;
|
|
16244
|
+
var SQL_PATTERNS, SAFE_WHERE_OPERATORS, warnedSqlFragmentContexts, queryCache, reorderCache, REORDER_CACHE_MAX = 500;
|
|
16212
16245
|
var init_client = __esm(() => {
|
|
16213
16246
|
init_config();
|
|
16214
16247
|
init_db();
|
|
@@ -16243,7 +16276,9 @@ var init_client = __esm(() => {
|
|
|
16243
16276
|
"between",
|
|
16244
16277
|
"not between"
|
|
16245
16278
|
]);
|
|
16279
|
+
warnedSqlFragmentContexts = new Set;
|
|
16246
16280
|
queryCache = new QueryCache;
|
|
16281
|
+
reorderCache = new Map;
|
|
16247
16282
|
});
|
|
16248
16283
|
|
|
16249
16284
|
// src/actions/cache.ts
|
|
@@ -23821,8 +23856,15 @@ function getModelFromRegistry(name) {
|
|
|
23821
23856
|
return _getModel(name);
|
|
23822
23857
|
}
|
|
23823
23858
|
function toPostgresPlaceholders(sql2) {
|
|
23859
|
+
const hit = pgPlaceholderCache.get(sql2);
|
|
23860
|
+
if (hit !== undefined)
|
|
23861
|
+
return hit;
|
|
23824
23862
|
let i2 = 0;
|
|
23825
|
-
|
|
23863
|
+
const out = sql2.replace(/\?/g, () => `$${++i2}`);
|
|
23864
|
+
if (pgPlaceholderCache.size >= PG_PLACEHOLDER_CACHE_MAX)
|
|
23865
|
+
pgPlaceholderCache.clear();
|
|
23866
|
+
pgPlaceholderCache.set(sql2, out);
|
|
23867
|
+
return out;
|
|
23826
23868
|
}
|
|
23827
23869
|
function extractChanges(res) {
|
|
23828
23870
|
if (res == null)
|
|
@@ -23954,21 +23996,25 @@ function timestampsEnabled(definition) {
|
|
|
23954
23996
|
return Boolean(t2?.useTimestamps || t2?.timestampable);
|
|
23955
23997
|
}
|
|
23956
23998
|
function collectBelongsToManyKeys(definition) {
|
|
23999
|
+
const cached = btmKeysCache.get(definition);
|
|
24000
|
+
if (cached)
|
|
24001
|
+
return cached;
|
|
23957
24002
|
const keys = new Set;
|
|
23958
24003
|
const rel = definition.belongsToMany;
|
|
23959
|
-
if (
|
|
23960
|
-
|
|
23961
|
-
|
|
23962
|
-
|
|
23963
|
-
|
|
23964
|
-
|
|
23965
|
-
|
|
23966
|
-
|
|
23967
|
-
}
|
|
23968
|
-
|
|
23969
|
-
|
|
23970
|
-
|
|
24004
|
+
if (rel) {
|
|
24005
|
+
if (Array.isArray(rel)) {
|
|
24006
|
+
for (const item of rel) {
|
|
24007
|
+
if (typeof item === "string")
|
|
24008
|
+
keys.add(item.toLowerCase());
|
|
24009
|
+
else if (item && typeof item === "object" && item.model)
|
|
24010
|
+
keys.add(item.model.toLowerCase());
|
|
24011
|
+
}
|
|
24012
|
+
} else if (typeof rel === "object") {
|
|
24013
|
+
for (const k2 of Object.keys(rel))
|
|
24014
|
+
keys.add(k2);
|
|
24015
|
+
}
|
|
23971
24016
|
}
|
|
24017
|
+
btmKeysCache.set(definition, keys);
|
|
23972
24018
|
return keys;
|
|
23973
24019
|
}
|
|
23974
24020
|
|
|
@@ -25203,7 +25249,14 @@ class ModelQueryBuilder {
|
|
|
25203
25249
|
sql2 += ` WHERE ${whereBody}`;
|
|
25204
25250
|
}
|
|
25205
25251
|
const row = await exec.get(sql2, params);
|
|
25206
|
-
|
|
25252
|
+
const v2 = row?.v;
|
|
25253
|
+
if (v2 == null)
|
|
25254
|
+
return null;
|
|
25255
|
+
if (typeof v2 === "string") {
|
|
25256
|
+
const n2 = Number(v2);
|
|
25257
|
+
return v2.trim() !== "" && !Number.isNaN(n2) ? n2 : v2;
|
|
25258
|
+
}
|
|
25259
|
+
return v2;
|
|
25207
25260
|
}
|
|
25208
25261
|
max(column) {
|
|
25209
25262
|
return this.aggregate("MAX", column);
|
|
@@ -25212,10 +25265,10 @@ class ModelQueryBuilder {
|
|
|
25212
25265
|
return this.aggregate("MIN", column);
|
|
25213
25266
|
}
|
|
25214
25267
|
async avg(column) {
|
|
25215
|
-
return await this.aggregate("AVG", column) || 0;
|
|
25268
|
+
return Number(await this.aggregate("AVG", column) ?? 0) || 0;
|
|
25216
25269
|
}
|
|
25217
25270
|
async sum(column) {
|
|
25218
|
-
return await this.aggregate("SUM", column) || 0;
|
|
25271
|
+
return Number(await this.aggregate("SUM", column) ?? 0) || 0;
|
|
25219
25272
|
}
|
|
25220
25273
|
async delete() {
|
|
25221
25274
|
const exec = getExecutor();
|
|
@@ -25473,11 +25526,13 @@ async function seedModel(definition, count, faker) {
|
|
|
25473
25526
|
await exec.run(`INSERT INTO ${definition.table} (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`, Object.values(data));
|
|
25474
25527
|
}
|
|
25475
25528
|
}
|
|
25476
|
-
var SAFE_SQL_IDENTIFIER, _getModel = null, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000;
|
|
25529
|
+
var SAFE_SQL_IDENTIFIER, _getModel = null, pgPlaceholderCache, PG_PLACEHOLDER_CACHE_MAX = 500, globalDb = null, _executor = null, _executorForDb = null, _executorDialect = null, _executorDatabase = null, SOFT_DELETE_COLUMN = "deleted_at", BTM_RELATED_ALIAS = "__btm_rel__", btmKeysCache, snakeCaseCache, tableNameCache, relationCache, RELATION_CACHE_MAX = 1000;
|
|
25477
25530
|
var init_orm = __esm(() => {
|
|
25478
25531
|
init_config();
|
|
25479
25532
|
init_db();
|
|
25480
25533
|
SAFE_SQL_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/i;
|
|
25534
|
+
pgPlaceholderCache = new Map;
|
|
25535
|
+
btmKeysCache = new WeakMap;
|
|
25481
25536
|
snakeCaseCache = new Map;
|
|
25482
25537
|
tableNameCache = new Map;
|
|
25483
25538
|
relationCache = new Map;
|
package/package.json
CHANGED