bun-query-builder 0.2.40 → 0.2.43

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
@@ -12856,7 +12856,10 @@ function warnOnceBareSqlFragment(context) {
12856
12856
  if (warnedSqlFragmentContexts.has(context))
12857
12857
  return;
12858
12858
  warnedSqlFragmentContexts.add(context);
12859
- 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.`);
12859
+ console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer the \`raw\`...\`\` tagged template, which escapes interpolated ` + `values for the configured dialect \u2014 concatenating request input into SQL ` + `is an injection vector. This will become a hard error in a future release.`);
12860
+ }
12861
+ function escapeStringLiteral(value, dialect = config5.dialect) {
12862
+ return isMysqlLike(dialect) ? value.replace(/\\/g, "\\\\").replace(/'/g, "''") : value.replace(/'/g, "''");
12860
12863
  }
12861
12864
  function formatSubqueryValue(val) {
12862
12865
  if (val === null)
@@ -12866,7 +12869,7 @@ function formatSubqueryValue(val) {
12866
12869
  if (typeof val === "boolean")
12867
12870
  return val ? "1" : "0";
12868
12871
  if (typeof val === "string")
12869
- return `'${val.replace(/'/g, "''")}'`;
12872
+ return `'${escapeStringLiteral(val)}'`;
12870
12873
  if (val instanceof Date)
12871
12874
  return `'${val.toISOString()}'`;
12872
12875
  throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
@@ -14487,11 +14490,13 @@ function createQueryBuilder(state) {
14487
14490
  const dialect = config5.dialect;
14488
14491
  const idx = whereParams.length + 1;
14489
14492
  if (dialect === "postgres") {
14493
+ const needsJsonbLift = typeof json === "number" || typeof json === "boolean";
14494
+ const operand = needsJsonbLift ? `to_jsonb(${getPlaceholder(idx)})` : getPlaceholder(idx);
14490
14495
  if (config5.sql?.jsonContainsMode === "function")
14491
- pushWhere("AND", `jsonb_contains(${column}, ${getPlaceholder(idx)})`);
14496
+ pushWhere("AND", `jsonb_contains(${column}, ${operand})`);
14492
14497
  else
14493
- pushWhere("AND", `${column} @> ${getPlaceholder(idx)}`);
14494
- whereParams.push(JSON.stringify(json));
14498
+ pushWhere("AND", `${column} @> ${operand}`);
14499
+ whereParams.push(json);
14495
14500
  } else if (isMysqlLike(dialect)) {
14496
14501
  pushWhere("AND", `JSON_CONTAINS(${column}, ${getPlaceholder(idx)})`);
14497
14502
  whereParams.push(JSON.stringify(json));
@@ -32354,7 +32359,7 @@ function getPrefix() {
32354
32359
  }
32355
32360
  var prefix = getPrefix();
32356
32361
  // package.json
32357
- var version2 = "0.2.40";
32362
+ var version2 = "0.2.43";
32358
32363
 
32359
32364
  // bin/cli.ts
32360
32365
  init_actions();
package/dist/client.d.ts CHANGED
@@ -2,7 +2,7 @@ import { config } from './config';
2
2
  import { resetConnection } from './db';
3
3
  import type { DatabaseSchema } from './schema';
4
4
  import type { DriverConnection } from './db';
5
- import type { QueryHooks } from './types';
5
+ import type { QueryHooks, SupportedDialect } from './types';
6
6
  import type { SchemaMeta } from './meta';
7
7
  /**
8
8
  * # `raw`
@@ -31,6 +31,24 @@ import type { SchemaMeta } from './meta';
31
31
  * ```
32
32
  */
33
33
  export declare function raw(strings: TemplateStringsArray | string, ...values: unknown[]): RawExpression;
34
+ /**
35
+ * Format a value for safe interpolation into a relationship-subquery
36
+ * fragment. Numbers / booleans / null pass through; everything else is
37
+ * rejected.
38
+ *
39
+ * **Strings are escaped for the configured dialect, not just for ANSI.**
40
+ * Doubling the single quote is correct on Postgres and SQLite and is not
41
+ * sufficient on the MySQL family, where a backslash is itself an escape
42
+ * character unless NO_BACKSLASH_ESCAPES is set. Doubling alone lets
43
+ * `x\'; DROP TABLE t; --` through: it becomes `'x\''; DROP TABLE t; --'`,
44
+ * MySQL reads `\'` as a literal quote inside the string, the next quote closes
45
+ * it, and the remainder executes as its own statement.
46
+ *
47
+ * So on a MySQL-like dialect the backslash is doubled first. Order matters:
48
+ * escaping quotes first and backslashes second would re-escape the backslash
49
+ * this function had just introduced.
50
+ */
51
+ export declare function escapeStringLiteral(value: string, dialect?: SupportedDialect): string;
34
52
  // eslint-disable-next-line pickier/no-unused-vars
35
53
  export declare function createQueryBuilder<DB extends DatabaseSchema<any>>(state?: Partial<InternalState>): QueryBuilder<DB>;
36
54
  /**
package/dist/orm.d.ts CHANGED
@@ -375,10 +375,21 @@ declare type FillableKeys<TDef extends ModelDefinition> = {
375
375
  * The database's own nullability is still the authority: writing null to a NOT
376
376
  * NULL column fails there, as it should.
377
377
  */
378
- declare type FillableAttributes<TDef extends ModelDefinition> = Partial<Pick<
379
- { [K in keyof ModelAttributes<TDef>]: ModelAttributes<TDef>[K] | null },
378
+ type FillableAttributes<TDef extends ModelDefinition> = Partial<Pick<
379
+ {
380
+ [K in keyof ModelAttributes<TDef>]:
381
+ // Already nullable: nothing to add.
382
+ null extends ModelAttributes<TDef>[K]
383
+ ? ModelAttributes<TDef>[K]
384
+ // Optional, so the column is nullable in the database: `null` is how a
385
+ // write CLEARS it, which is different from omitting the key.
386
+ : undefined extends ModelAttributes<TDef>[K]
387
+ ? ModelAttributes<TDef>[K] | null
388
+ // Required: null stays rejected, as the NOT NULL column would reject it.
389
+ : ModelAttributes<TDef>[K]
390
+ },
380
391
  FillableKeys<TDef> | SnakeCase<FillableKeys<TDef>> | BelongsToKeys<TDef>
381
- >>;
392
+ >>
382
393
  // Numeric attribute columns - constrains aggregate methods (sum, avg, etc.)
383
394
  declare type NumericColumns<TDef extends ModelDefinition> = {
384
395
  [K in AttributeKeys<TDef>]: InferAttributeType<TDef['attributes'][K]> extends number ? K : never
package/dist/src/index.js CHANGED
@@ -12855,7 +12855,10 @@ function warnOnceBareSqlFragment(context) {
12855
12855
  if (warnedSqlFragmentContexts.has(context))
12856
12856
  return;
12857
12857
  warnedSqlFragmentContexts.add(context);
12858
- console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer \`sql\`...\`\` tagged-template fragments so values are parameterised ` + `instead of concatenated \u2014 concatenating request input into SQL is an ` + `injection vector. This will become a hard error in a future release.`);
12858
+ console.warn(`[query-builder] ${context}: bare string passed to a *Raw method. ` + `Prefer the \`raw\`...\`\` tagged template, which escapes interpolated ` + `values for the configured dialect \u2014 concatenating request input into SQL ` + `is an injection vector. This will become a hard error in a future release.`);
12859
+ }
12860
+ function escapeStringLiteral(value, dialect = config5.dialect) {
12861
+ return isMysqlLike(dialect) ? value.replace(/\\/g, "\\\\").replace(/'/g, "''") : value.replace(/'/g, "''");
12859
12862
  }
12860
12863
  function formatSubqueryValue(val) {
12861
12864
  if (val === null)
@@ -12865,7 +12868,7 @@ function formatSubqueryValue(val) {
12865
12868
  if (typeof val === "boolean")
12866
12869
  return val ? "1" : "0";
12867
12870
  if (typeof val === "string")
12868
- return `'${val.replace(/'/g, "''")}'`;
12871
+ return `'${escapeStringLiteral(val)}'`;
12869
12872
  if (val instanceof Date)
12870
12873
  return `'${val.toISOString()}'`;
12871
12874
  throw new TypeError(`[query-builder] subquery condition: refusing to interpolate value of type ${typeof val}`);
@@ -14486,11 +14489,13 @@ function createQueryBuilder(state) {
14486
14489
  const dialect = config5.dialect;
14487
14490
  const idx = whereParams.length + 1;
14488
14491
  if (dialect === "postgres") {
14492
+ const needsJsonbLift = typeof json === "number" || typeof json === "boolean";
14493
+ const operand = needsJsonbLift ? `to_jsonb(${getPlaceholder(idx)})` : getPlaceholder(idx);
14489
14494
  if (config5.sql?.jsonContainsMode === "function")
14490
- pushWhere("AND", `jsonb_contains(${column}, ${getPlaceholder(idx)})`);
14495
+ pushWhere("AND", `jsonb_contains(${column}, ${operand})`);
14491
14496
  else
14492
- pushWhere("AND", `${column} @> ${getPlaceholder(idx)}`);
14493
- whereParams.push(JSON.stringify(json));
14497
+ pushWhere("AND", `${column} @> ${operand}`);
14498
+ whereParams.push(json);
14494
14499
  } else if (isMysqlLike(dialect)) {
14495
14500
  pushWhere("AND", `JSON_CONTAINS(${column}, ${getPlaceholder(idx)})`);
14496
14501
  whereParams.push(JSON.stringify(json));
@@ -30380,6 +30385,7 @@ export {
30380
30385
  queryExplainAll as explainAllQueries,
30381
30386
  explain,
30382
30387
  executeMigration,
30388
+ escapeStringLiteral,
30383
30389
  dynamo,
30384
30390
  dumpDatabase,
30385
30391
  defineSeeder,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-query-builder",
3
3
  "type": "module",
4
- "version": "0.2.40",
4
+ "version": "0.2.43",
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",