@bunnykit/orm 0.1.15 → 0.1.17

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.
@@ -20,8 +20,8 @@ export declare class Connection {
20
20
  withoutSchema(): Connection;
21
21
  qualifyTable(table: string): string;
22
22
  private quoteIdentifier;
23
- query(sqlString: string): Promise<any[]>;
24
- run(sqlString: string): Promise<any>;
23
+ query(sqlString: string, bindings?: any[]): Promise<any[]>;
24
+ run(sqlString: string, bindings?: any[]): Promise<any>;
25
25
  beginTransaction(): Promise<void>;
26
26
  commit(): Promise<void>;
27
27
  rollback(): Promise<void>;
@@ -75,11 +75,11 @@ export class Connection {
75
75
  quoteIdentifier(value) {
76
76
  return `"${value.replace(/"/g, '""')}"`;
77
77
  }
78
- async query(sqlString) {
79
- return (await this.driver.unsafe(sqlString));
78
+ async query(sqlString, bindings) {
79
+ return (await this.driver.unsafe(sqlString, bindings));
80
80
  }
81
- async run(sqlString) {
82
- return await this.driver.unsafe(sqlString);
81
+ async run(sqlString, bindings) {
82
+ return await this.driver.unsafe(sqlString, bindings);
83
83
  }
84
84
  async beginTransaction() {
85
85
  await this.driver.unsafe("BEGIN");
@@ -105,7 +105,7 @@ export class Connection {
105
105
  return await this.transaction(callback);
106
106
  }
107
107
  return await this.transaction(async (connection) => {
108
- await connection.run(`SET LOCAL ${setting} = ${connection.getGrammar().escape(tenantId)}`);
108
+ await connection.run(`SET LOCAL ${setting} = ${connection.getGrammar().placeholder(1)}`, [tenantId]);
109
109
  return await callback(connection);
110
110
  });
111
111
  }
@@ -31,6 +31,7 @@ export declare class Builder<T = Record<string, any>> {
31
31
  fromRaw?: string;
32
32
  updateJoins: string[];
33
33
  bindings: any[];
34
+ private parameterize;
34
35
  constructor(connection: Connection, table: string);
35
36
  private get grammar();
36
37
  setModel(model: ModelConstructor): this;
@@ -130,6 +131,7 @@ export declare class Builder<T = Record<string, any>> {
130
131
  clone(): Builder<T>;
131
132
  wrapColumn(value: string): string;
132
133
  escapeValue(value: any): string;
134
+ private addBinding;
133
135
  private compileWhereClause;
134
136
  private compileWheres;
135
137
  private compileNestedWheres;
@@ -158,7 +160,7 @@ export declare class Builder<T = Record<string, any>> {
158
160
  paginate(perPage?: number, page?: number): Promise<Paginator<T>>;
159
161
  chunk(count: number, callback: (items: T[]) => void | Promise<void>): Promise<void>;
160
162
  each(count: number, callback: (item: T) => void | Promise<void>): Promise<void>;
161
- cursor(keyset?: Record<string, any>): AsyncGenerator<T>;
163
+ cursor(chunkSize?: number): AsyncGenerator<T>;
162
164
  lazy(count?: number): AsyncGenerator<T>;
163
165
  insert(data: ModelAttributeInput<T> | ModelAttributeInput<T>[]): Promise<any>;
164
166
  insertGetId(data: ModelAttributeInput<T>, idColumn?: ModelColumn<T>): Promise<any>;
@@ -19,6 +19,7 @@ export class Builder {
19
19
  fromRaw;
20
20
  updateJoins = [];
21
21
  bindings = [];
22
+ parameterize = false;
22
23
  constructor(connection, table) {
23
24
  this.connection = connection;
24
25
  this.tableName = table;
@@ -258,11 +259,11 @@ export class Builder {
258
259
  return this;
259
260
  }
260
261
  having(column, operator, value) {
261
- this.havings.push({ sql: `${this.grammar.wrap(column)} ${operator} ${this.grammar.escape(value)}`, boolean: "and" });
262
+ this.havings.push({ column, operator, value, boolean: "and" });
262
263
  return this;
263
264
  }
264
265
  orHaving(column, operator, value) {
265
- this.havings.push({ sql: `${this.grammar.wrap(column)} ${operator} ${this.grammar.escape(value)}`, boolean: "or" });
266
+ this.havings.push({ column, operator, value, boolean: "or" });
266
267
  return this;
267
268
  }
268
269
  havingRaw(sql, boolean = "and") {
@@ -465,13 +466,21 @@ export class Builder {
465
466
  escapeValue(value) {
466
467
  return this.grammar.escape(value);
467
468
  }
469
+ addBinding(value) {
470
+ this.bindings.push(value);
471
+ return this.grammar.placeholder(this.bindings.length);
472
+ }
468
473
  compileWhereClause(where, prefix) {
469
474
  if (where.type === "basic") {
470
- return `${prefix} ${this.grammar.wrap(where.column)} ${where.operator} ${this.grammar.escape(where.value)}`;
475
+ const value = this.parameterize ? this.addBinding(where.value) : this.grammar.escape(where.value);
476
+ return `${prefix} ${this.grammar.wrap(where.column)} ${where.operator} ${value}`;
471
477
  }
472
478
  else if (where.type === "in") {
473
479
  const op = where.operator === "NOT IN" ? "NOT IN" : "IN";
474
- return `${prefix} ${this.grammar.wrap(where.column)} ${op} (${where.value.map((v) => this.grammar.escape(v)).join(", ")})`;
480
+ const values = this.parameterize
481
+ ? where.value.map((v) => this.addBinding(v)).join(", ")
482
+ : where.value.map((v) => this.grammar.escape(v)).join(", ");
483
+ return `${prefix} ${this.grammar.wrap(where.column)} ${op} (${values})`;
475
484
  }
476
485
  else if (where.type === "null") {
477
486
  const op = where.operator === "NOT NULL" ? "IS NOT NULL" : "IS NULL";
@@ -479,7 +488,9 @@ export class Builder {
479
488
  }
480
489
  else if (where.type === "between") {
481
490
  const op = where.operator === "NOT BETWEEN" ? "NOT BETWEEN" : "BETWEEN";
482
- return `${prefix} ${this.grammar.wrap(where.column)} ${op} ${this.grammar.escape(where.value[0])} AND ${this.grammar.escape(where.value[1])}`;
491
+ const low = this.parameterize ? this.addBinding(where.value[0]) : this.grammar.escape(where.value[0]);
492
+ const high = this.parameterize ? this.addBinding(where.value[1]) : this.grammar.escape(where.value[1]);
493
+ return `${prefix} ${this.grammar.wrap(where.column)} ${op} ${low} AND ${high}`;
483
494
  }
484
495
  else if (where.type === "raw") {
485
496
  return `${prefix} ${where.column}`;
@@ -528,7 +539,11 @@ export class Builder {
528
539
  return "";
529
540
  const clauses = this.havings.map((h, index) => {
530
541
  const prefix = index === 0 ? "" : h.boolean.toUpperCase() + " ";
531
- return prefix + h.sql;
542
+ if (h.sql) {
543
+ return prefix + h.sql;
544
+ }
545
+ const value = this.parameterize ? this.addBinding(h.value) : this.grammar.escape(h.value);
546
+ return prefix + `${this.grammar.wrap(h.column)} ${h.operator} ${value}`;
532
547
  });
533
548
  return `HAVING ${clauses.join(" ")}`;
534
549
  }
@@ -567,7 +582,11 @@ export class Builder {
567
582
  return sql.replace(/\s+/g, " ").trim();
568
583
  }
569
584
  async get() {
570
- const results = await this.connection.query(this.toSql());
585
+ this.bindings = [];
586
+ this.parameterize = true;
587
+ const sql = this.toSql();
588
+ this.parameterize = false;
589
+ const results = await this.connection.query(sql, this.bindings);
571
590
  const rows = Array.from(results);
572
591
  if (this.model) {
573
592
  const models = rows.map((row) => {
@@ -699,34 +718,40 @@ export class Builder {
699
718
  }
700
719
  });
701
720
  }
702
- async *cursor(keyset) {
721
+ async *cursor(chunkSize = 1000) {
703
722
  const model = this.model;
704
723
  const primaryKey = model ? model.primaryKey || "id" : "id";
705
724
  const orderColumn = this.orders[0]?.column || primaryKey;
706
725
  const orderDirection = this.orders[0]?.direction || "asc";
707
- const builder = this.clone();
708
- builder.orders = [{ column: orderColumn, direction: orderDirection }];
709
- builder.offsetValue = undefined;
710
- if (keyset) {
711
- const op = orderDirection === "asc" ? ">" : "<";
712
- builder.wheres.push({
713
- type: "basic",
714
- column: orderColumn,
715
- operator: op,
716
- value: keyset[orderColumn],
717
- boolean: "and",
718
- scope: undefined,
719
- });
720
- }
721
- const items = await builder.limit(1).get();
722
- if (items.length === 0)
723
- return;
724
- yield items[0];
725
- const nextKeyset = items[0] && typeof items[0] === "object"
726
- ? { [orderColumn]: items[0][orderColumn] }
727
- : undefined;
728
- if (nextKeyset) {
729
- yield* this.cursor(nextKeyset);
726
+ let lastValue = undefined;
727
+ while (true) {
728
+ const builder = this.clone();
729
+ builder.orders = [{ column: orderColumn, direction: orderDirection }];
730
+ builder.offsetValue = undefined;
731
+ builder.limitValue = chunkSize;
732
+ if (lastValue !== undefined) {
733
+ const op = orderDirection === "asc" ? ">" : "<";
734
+ builder.wheres.push({
735
+ type: "basic",
736
+ column: orderColumn,
737
+ operator: op,
738
+ value: lastValue,
739
+ boolean: "and",
740
+ scope: undefined,
741
+ });
742
+ }
743
+ const items = await builder.get();
744
+ if (items.length === 0)
745
+ break;
746
+ for (const item of items) {
747
+ yield item;
748
+ }
749
+ if (items.length < chunkSize)
750
+ break;
751
+ const lastItem = items[items.length - 1];
752
+ lastValue = lastItem && typeof lastItem === "object"
753
+ ? lastItem[orderColumn]
754
+ : undefined;
730
755
  }
731
756
  }
732
757
  async *lazy(count = 1000) {
@@ -748,11 +773,15 @@ export class Builder {
748
773
  if (records.length === 0)
749
774
  return;
750
775
  const columns = Object.keys(records[0]);
776
+ const bindings = [];
751
777
  const values = records.map((record) => {
752
- return `(${columns.map((col) => this.grammar.escape(record[col])).join(", ")})`;
778
+ return `(${columns.map((col) => {
779
+ bindings.push(record[col]);
780
+ return this.grammar.placeholder(bindings.length);
781
+ }).join(", ")})`;
753
782
  });
754
783
  const sql = `INSERT INTO ${this.grammar.wrap(this.tableName)} (${columns.map((c) => this.grammar.wrap(c)).join(", ")}) VALUES ${values.join(", ")}`;
755
- return await this.connection.run(sql);
784
+ return await this.connection.run(sql, bindings);
756
785
  }
757
786
  async insertGetId(data, idColumn = "id") {
758
787
  const result = await this.insert(data);
@@ -763,43 +792,65 @@ export class Builder {
763
792
  if (records.length === 0)
764
793
  return;
765
794
  const columns = Object.keys(records[0]);
795
+ const bindings = [];
766
796
  const values = records.map((record) => {
767
- return `(${columns.map((col) => this.grammar.escape(record[col])).join(", ")})`;
797
+ return `(${columns.map((col) => {
798
+ bindings.push(record[col]);
799
+ return this.grammar.placeholder(bindings.length);
800
+ }).join(", ")})`;
768
801
  });
769
802
  const sql = this.grammar.compileInsertOrIgnore(this.grammar.wrap(this.tableName), columns, values);
770
- return await this.connection.run(sql);
803
+ return await this.connection.run(sql, bindings);
771
804
  }
772
805
  async upsert(data, uniqueBy, updateColumns) {
773
806
  const records = Array.isArray(data) ? data : [data];
774
807
  if (records.length === 0)
775
808
  return;
776
809
  const columns = Object.keys(records[0]);
810
+ const bindings = [];
777
811
  const values = records.map((record) => {
778
- return `(${columns.map((col) => this.grammar.escape(record[col])).join(", ")})`;
812
+ return `(${columns.map((col) => {
813
+ bindings.push(record[col]);
814
+ return this.grammar.placeholder(bindings.length);
815
+ }).join(", ")})`;
779
816
  });
780
817
  const uniqueCols = Array.isArray(uniqueBy) ? uniqueBy : [uniqueBy];
781
818
  const updateCols = updateColumns ?? columns.filter((c) => !uniqueCols.includes(c));
782
819
  const sql = this.grammar.compileUpsert(this.grammar.wrap(this.tableName), columns, values, uniqueCols, updateCols);
783
- return await this.connection.run(sql);
820
+ return await this.connection.run(sql, bindings);
784
821
  }
785
822
  async update(data) {
786
- const sets = Object.entries(data)
787
- .map(([key, value]) => `${this.grammar.wrap(key)} = ${this.grammar.escape(value)}`)
788
- .join(", ");
789
- const sql = this.grammar.compileUpdate(this.grammar.wrap(this.tableName), sets.split(", "), this.compileWheres(), this.updateJoins);
790
- return await this.connection.run(sql);
823
+ this.bindings = [];
824
+ this.parameterize = true;
825
+ const sets = Object.entries(data).map(([key, value]) => {
826
+ this.bindings.push(value);
827
+ return `${this.grammar.wrap(key)} = ${this.grammar.placeholder(this.bindings.length)}`;
828
+ });
829
+ const whereSql = this.compileWheres();
830
+ this.parameterize = false;
831
+ const sql = this.grammar.compileUpdate(this.grammar.wrap(this.tableName), sets, whereSql, this.updateJoins);
832
+ return await this.connection.run(sql, this.bindings);
791
833
  }
792
834
  async delete() {
793
- const sql = this.grammar.compileDelete(this.grammar.wrap(this.tableName), this.compileWheres(), this.updateJoins, this.limitValue);
794
- return await this.connection.run(sql);
835
+ this.bindings = [];
836
+ this.parameterize = true;
837
+ const whereSql = this.compileWheres();
838
+ this.parameterize = false;
839
+ const sql = this.grammar.compileDelete(this.grammar.wrap(this.tableName), whereSql, this.updateJoins, this.limitValue);
840
+ return await this.connection.run(sql, this.bindings);
795
841
  }
796
842
  async increment(column, amount = 1, extra = {}) {
843
+ this.bindings = [];
844
+ this.parameterize = true;
797
845
  const sets = [`${this.grammar.wrap(column)} = ${this.grammar.wrap(column)} + ${amount}`];
798
846
  for (const [key, value] of Object.entries(extra)) {
799
- sets.push(`${this.grammar.wrap(key)} = ${this.grammar.escape(value)}`);
847
+ this.bindings.push(value);
848
+ sets.push(`${this.grammar.wrap(key)} = ${this.grammar.placeholder(this.bindings.length)}`);
800
849
  }
801
- const sql = `UPDATE ${this.grammar.wrap(this.tableName)} SET ${sets.join(", ")} ${this.compileWheres()}`;
802
- return await this.connection.run(sql.trim());
850
+ const whereSql = this.compileWheres();
851
+ this.parameterize = false;
852
+ const sql = `UPDATE ${this.grammar.wrap(this.tableName)} SET ${sets.join(", ")} ${whereSql}`;
853
+ return await this.connection.run(sql.trim(), this.bindings);
803
854
  }
804
855
  async decrement(column, amount = 1, extra = {}) {
805
856
  return this.increment(column, -amount, extra);
@@ -841,8 +892,11 @@ export class Builder {
841
892
  throw new Error("dd() called — execution halted.");
842
893
  }
843
894
  async explain() {
895
+ this.bindings = [];
896
+ this.parameterize = true;
844
897
  const sql = this.grammar.compileExplain(this.toSql());
845
- const results = await this.connection.query(sql);
898
+ this.parameterize = false;
899
+ const results = await this.connection.query(sql, this.bindings);
846
900
  return Array.from(results);
847
901
  }
848
902
  take(count) {
@@ -1,6 +1,7 @@
1
1
  export declare abstract class Grammar {
2
2
  abstract wrap(value: string): string;
3
3
  wrapArray(values: string[]): string[];
4
+ abstract placeholder(index: number): string;
4
5
  escape(value: any): string;
5
6
  abstract compileRandomOrder(): string;
6
7
  compileOffset(offset: number, _limit?: number): string;
@@ -1,6 +1,7 @@
1
1
  import { Grammar } from "./Grammar.js";
2
2
  export declare class MySqlGrammar extends Grammar {
3
3
  wrap(value: string): string;
4
+ placeholder(_index: number): string;
4
5
  compileRandomOrder(): string;
5
6
  compileDateWhere(type: string, column: string, operator: string, value: any): string;
6
7
  compileInsertOrIgnore(table: string, columns: string[], values: string[]): string;
@@ -12,6 +12,9 @@ export class MySqlGrammar extends Grammar {
12
12
  return value;
13
13
  return `\`${value}\``;
14
14
  }
15
+ placeholder(_index) {
16
+ return "?";
17
+ }
15
18
  compileRandomOrder() {
16
19
  return "ORDER BY RAND()";
17
20
  }
@@ -1,6 +1,7 @@
1
1
  import { Grammar } from "./Grammar.js";
2
2
  export declare class PostgresGrammar extends Grammar {
3
3
  wrap(value: string): string;
4
+ placeholder(index: number): string;
4
5
  compileRandomOrder(): string;
5
6
  compileDateWhere(type: string, column: string, operator: string, value: any): string;
6
7
  compileInsertOrIgnore(table: string, columns: string[], values: string[]): string;
@@ -12,6 +12,9 @@ export class PostgresGrammar extends Grammar {
12
12
  return value;
13
13
  return `"${value}"`;
14
14
  }
15
+ placeholder(index) {
16
+ return `$${index}`;
17
+ }
15
18
  compileRandomOrder() {
16
19
  return "ORDER BY RANDOM()";
17
20
  }
@@ -1,6 +1,7 @@
1
1
  import { Grammar } from "./Grammar.js";
2
2
  export declare class SQLiteGrammar extends Grammar {
3
3
  wrap(value: string): string;
4
+ placeholder(_index: number): string;
4
5
  compileRandomOrder(): string;
5
6
  compileOffset(offset: number, limit?: number): string;
6
7
  compileDateWhere(type: string, column: string, operator: string, value: any): string;
@@ -12,6 +12,9 @@ export class SQLiteGrammar extends Grammar {
12
12
  return value;
13
13
  return `"${value}"`;
14
14
  }
15
+ placeholder(_index) {
16
+ return "?";
17
+ }
15
18
  compileRandomOrder() {
16
19
  return "ORDER BY RANDOM()";
17
20
  }
@@ -41,7 +41,10 @@ export interface OrderClause {
41
41
  direction: "asc" | "desc";
42
42
  }
43
43
  export interface HavingClause {
44
- sql: string;
44
+ column?: string;
45
+ operator?: string;
46
+ value?: any;
47
+ sql?: string;
45
48
  boolean: "and" | "or";
46
49
  }
47
50
  export interface UnionClause {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunnykit/orm",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "An Eloquent-inspired ORM for Bun's native SQL client supporting SQLite, MySQL, and PostgreSQL.",
5
5
  "license": "MIT",
6
6
  "packageManager": "bun@1.3.12",