@bunnykit/orm 0.1.22 → 0.1.23

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.
@@ -9,6 +9,7 @@ export declare class Connection {
9
9
  private schema?;
10
10
  private ownsDriver;
11
11
  private transactionDepth;
12
+ private savepointId;
12
13
  constructor(config: ConnectionConfig, options?: {
13
14
  driver?: SQL;
14
15
  schema?: string;
@@ -10,6 +10,7 @@ export class Connection {
10
10
  schema;
11
11
  ownsDriver;
12
12
  transactionDepth = 0;
13
+ savepointId = 0;
13
14
  constructor(config, options = {}) {
14
15
  this.config = config;
15
16
  this.schema = options.schema || ("schema" in config ? config.schema : undefined);
@@ -83,16 +84,36 @@ export class Connection {
83
84
  return await this.driver.unsafe(sqlString, bindings);
84
85
  }
85
86
  async beginTransaction() {
86
- await this.driver.unsafe("BEGIN");
87
+ if (this.transactionDepth === 0) {
88
+ await this.driver.unsafe("BEGIN");
89
+ }
90
+ else {
91
+ await this.driver.unsafe(`SAVEPOINT bunny_trans_${++this.savepointId}`);
92
+ }
87
93
  this.transactionDepth++;
88
94
  }
89
95
  async commit() {
90
- await this.driver.unsafe("COMMIT");
91
- this.transactionDepth = Math.max(0, this.transactionDepth - 1);
96
+ if (this.transactionDepth <= 0)
97
+ return;
98
+ if (this.transactionDepth === 1) {
99
+ await this.driver.unsafe("COMMIT");
100
+ }
101
+ else {
102
+ await this.driver.unsafe(`RELEASE SAVEPOINT bunny_trans_${this.savepointId--}`);
103
+ }
104
+ this.transactionDepth--;
92
105
  }
93
106
  async rollback() {
94
- await this.driver.unsafe("ROLLBACK");
95
- this.transactionDepth = Math.max(0, this.transactionDepth - 1);
107
+ if (this.transactionDepth <= 0)
108
+ return;
109
+ if (this.transactionDepth === 1) {
110
+ await this.driver.unsafe("ROLLBACK");
111
+ }
112
+ else {
113
+ await this.driver.unsafe(`ROLLBACK TO SAVEPOINT bunny_trans_${this.savepointId}`);
114
+ await this.driver.unsafe(`RELEASE SAVEPOINT bunny_trans_${this.savepointId--}`);
115
+ }
116
+ this.transactionDepth--;
96
117
  }
97
118
  isInTransaction() {
98
119
  return this.transactionDepth > 0;
@@ -195,30 +195,30 @@ export class Migrator {
195
195
  const statements = [];
196
196
  for (const row of tables) {
197
197
  const table = row[key];
198
- const createRows = await this.connection.query(`SHOW CREATE TABLE ${table}`);
198
+ const createRows = await this.connection.query(`SHOW CREATE TABLE ${this.connection.getGrammar().wrap(table)}`);
199
199
  statements.push(`${createRows[0]["Create Table"]};`);
200
200
  }
201
201
  return statements.join("\n\n") + "\n";
202
202
  }
203
203
  const schema = this.connection.getSchema() || "public";
204
- const tables = await this.connection.query(`SELECT table_name FROM information_schema.tables WHERE table_schema = '${schema}' AND table_type = 'BASE TABLE' ORDER BY table_name`);
204
+ const tables = await this.connection.query("SELECT table_name FROM information_schema.tables WHERE table_schema = $1 AND table_type = 'BASE TABLE' ORDER BY table_name", [schema]);
205
205
  const statements = [];
206
206
  for (const tableRow of tables) {
207
207
  const table = tableRow.table_name;
208
208
  const columns = await this.connection.query(`SELECT column_name, data_type, is_nullable, column_default, character_maximum_length, numeric_precision, numeric_scale
209
209
  FROM information_schema.columns
210
- WHERE table_schema = '${schema}' AND table_name = '${table}'
211
- ORDER BY ordinal_position`);
210
+ WHERE table_schema = $1 AND table_name = $2
211
+ ORDER BY ordinal_position`, [schema, table]);
212
212
  const primaryKeys = await this.connection.query(`SELECT kcu.column_name
213
213
  FROM information_schema.table_constraints tc
214
214
  JOIN information_schema.key_column_usage kcu
215
215
  ON tc.constraint_name = kcu.constraint_name
216
216
  AND tc.table_schema = kcu.table_schema
217
217
  AND tc.table_name = kcu.table_name
218
- WHERE tc.table_schema = '${schema}'
219
- AND tc.table_name = '${table}'
218
+ WHERE tc.table_schema = $1
219
+ AND tc.table_name = $2
220
220
  AND tc.constraint_type = 'PRIMARY KEY'
221
- ORDER BY kcu.ordinal_position`);
221
+ ORDER BY kcu.ordinal_position`, [schema, table]);
222
222
  const pkColumns = primaryKeys.map((row) => row.column_name);
223
223
  const columnSql = columns.map((column) => {
224
224
  let type = String(column.data_type).toUpperCase();
@@ -228,7 +228,7 @@ export class Migrator {
228
228
  else if ((type === "NUMERIC" || type === "DECIMAL") && column.numeric_precision) {
229
229
  type = `${type}(${column.numeric_precision}${column.numeric_scale ? `, ${column.numeric_scale}` : ""})`;
230
230
  }
231
- let sql = ` "${column.column_name}" ${type}`;
231
+ let sql = ` ${this.connection.getGrammar().wrap(column.column_name)} ${type}`;
232
232
  if (column.is_nullable === "NO")
233
233
  sql += " NOT NULL";
234
234
  if (column.column_default !== null && column.column_default !== undefined)
@@ -236,9 +236,9 @@ export class Migrator {
236
236
  return sql;
237
237
  });
238
238
  if (pkColumns.length > 0) {
239
- columnSql.push(` PRIMARY KEY (${pkColumns.map((column) => `"${column}"`).join(", ")})`);
239
+ columnSql.push(` PRIMARY KEY (${pkColumns.map((column) => this.connection.getGrammar().wrap(column)).join(", ")})`);
240
240
  }
241
- statements.push(`CREATE TABLE "${schema}"."${table}" (\n${columnSql.join(",\n")}\n);`);
241
+ statements.push(`CREATE TABLE ${this.connection.getGrammar().wrap(`${schema}.${table}`)} (\n${columnSql.join(",\n")}\n);`);
242
242
  }
243
243
  return statements.join("\n\n") + "\n";
244
244
  }
@@ -815,21 +815,22 @@ export class Model {
815
815
  async save() {
816
816
  const constructor = this.constructor;
817
817
  if (this.$exists) {
818
- await ObserverRegistry.dispatch("updating", this);
819
818
  await ObserverRegistry.dispatch("saving", this);
820
- if (constructor.timestamps) {
819
+ let dirty = this.getDirty();
820
+ if (Object.keys(dirty).length > 0 && constructor.timestamps) {
821
821
  this.$attributes["updated_at"] = this.freshTimestamp();
822
+ dirty = this.getDirty();
822
823
  }
823
- const dirty = this.getDirty();
824
824
  if (Object.keys(dirty).length > 0) {
825
+ await ObserverRegistry.dispatch("updating", this);
825
826
  const pk = this.getAttribute(constructor.primaryKey);
826
827
  const connection = this.getConnection();
827
828
  await new Builder(connection, connection.qualifyTable(constructor.getTable()))
828
829
  .where(constructor.primaryKey, pk)
829
830
  .update(dirty);
831
+ await ObserverRegistry.dispatch("updated", this);
830
832
  }
831
833
  this.$original = { ...this.$attributes };
832
- await ObserverRegistry.dispatch("updated", this);
833
834
  await ObserverRegistry.dispatch("saved", this);
834
835
  }
835
836
  else {
@@ -930,10 +931,10 @@ export class Model {
930
931
  }
931
932
  async delete() {
932
933
  const constructor = this.constructor;
933
- await ObserverRegistry.dispatch("deleting", this);
934
934
  const pk = this.getAttribute(constructor.primaryKey);
935
935
  if (!pk)
936
936
  return false;
937
+ await ObserverRegistry.dispatch("deleting", this);
937
938
  if (constructor.softDeletes) {
938
939
  const deletedAt = this.freshTimestamp();
939
940
  const connection = this.getConnection();
@@ -168,6 +168,7 @@ export declare class Builder<T = Record<string, any>> {
168
168
  insertGetId(data: ModelAttributeInput<T>, idColumn?: ModelColumn<T>): Promise<any>;
169
169
  insertOrIgnore(data: ModelAttributeInput<T> | ModelAttributeInput<T>[]): Promise<any>;
170
170
  upsert(data: ModelAttributeInput<T> | ModelAttributeInput<T>[], uniqueBy: ModelColumn<T> | ModelColumn<T>[], updateColumns?: ModelColumn<T>[]): Promise<any>;
171
+ private getUniformColumns;
171
172
  update(data: ModelAttributeInput<T>): Promise<any>;
172
173
  delete(): Promise<any>;
173
174
  increment(column: ModelColumn<T>, amount?: number, extra?: ModelAttributeInput<T>): Promise<any>;
@@ -717,10 +717,15 @@ export class Builder {
717
717
  return results.map((row) => row[column]);
718
718
  }
719
719
  async aggregate(sql, alias) {
720
- const model = this.model;
721
- this.model = undefined;
722
- const result = await this.select(`${sql} as ${alias}`).first();
723
- this.model = model;
720
+ const query = this.clone();
721
+ query.model = undefined;
722
+ query.columns = [`${sql} as ${alias}`];
723
+ query.orders = [];
724
+ query.limitValue = undefined;
725
+ query.offsetValue = undefined;
726
+ query.eagerLoads = [];
727
+ query.lockMode = undefined;
728
+ const result = await query.first();
724
729
  return result ? result[alias] : null;
725
730
  }
726
731
  async count(column = "*") {
@@ -739,7 +744,11 @@ export class Builder {
739
744
  return await this.aggregate(`MAX(${column})`, "max");
740
745
  }
741
746
  async paginate(perPage = 15, page = 1) {
742
- const total = await this.clone().count();
747
+ const countQuery = this.clone();
748
+ countQuery.limitValue = undefined;
749
+ countQuery.offsetValue = undefined;
750
+ countQuery.orders = [];
751
+ const total = await countQuery.count();
743
752
  const data = await this.clone().forPage(page, perPage).get();
744
753
  return {
745
754
  data,
@@ -867,7 +876,7 @@ export class Builder {
867
876
  const records = Array.isArray(data) ? data : [data];
868
877
  if (records.length === 0)
869
878
  return;
870
- const columns = Object.keys(records[0]);
879
+ const columns = this.getUniformColumns(records);
871
880
  const bindings = [];
872
881
  const values = records.map((record) => {
873
882
  return `(${columns.map((col) => {
@@ -886,7 +895,7 @@ export class Builder {
886
895
  const records = Array.isArray(data) ? data : [data];
887
896
  if (records.length === 0)
888
897
  return;
889
- const columns = Object.keys(records[0]);
898
+ const columns = this.getUniformColumns(records);
890
899
  const bindings = [];
891
900
  const values = records.map((record) => {
892
901
  return `(${columns.map((col) => {
@@ -901,7 +910,7 @@ export class Builder {
901
910
  const records = Array.isArray(data) ? data : [data];
902
911
  if (records.length === 0)
903
912
  return;
904
- const columns = Object.keys(records[0]);
913
+ const columns = this.getUniformColumns(records);
905
914
  const bindings = [];
906
915
  const values = records.map((record) => {
907
916
  return `(${columns.map((col) => {
@@ -914,6 +923,17 @@ export class Builder {
914
923
  const sql = this.grammar.compileUpsert(this.grammar.wrap(this.tableName), columns, values, uniqueCols, updateCols);
915
924
  return await this.connection.run(sql, bindings);
916
925
  }
926
+ getUniformColumns(records) {
927
+ const columns = Object.keys(records[0]);
928
+ const signature = [...columns].sort().join("\0");
929
+ for (let i = 1; i < records.length; i++) {
930
+ const recordSignature = Object.keys(records[i]).sort().join("\0");
931
+ if (recordSignature !== signature) {
932
+ throw new Error("Bulk insert records must have the same columns.");
933
+ }
934
+ }
935
+ return columns;
936
+ }
917
937
  async update(data) {
918
938
  this.bindings = [];
919
939
  this.parameterize = true;
@@ -10,7 +10,7 @@ export class MySqlGrammar extends Grammar {
10
10
  }
11
11
  if (value === "*")
12
12
  return value;
13
- return `\`${value}\``;
13
+ return `\`${value.replaceAll("`", "``")}\``;
14
14
  }
15
15
  placeholder(_index) {
16
16
  return "?";
@@ -10,7 +10,7 @@ export class PostgresGrammar extends Grammar {
10
10
  }
11
11
  if (value === "*")
12
12
  return value;
13
- return `"${value}"`;
13
+ return `"${value.replaceAll('"', '""')}"`;
14
14
  }
15
15
  placeholder(index) {
16
16
  return `$${index}`;
@@ -10,7 +10,7 @@ export class SQLiteGrammar extends Grammar {
10
10
  }
11
11
  if (value === "*")
12
12
  return value;
13
- return `"${value}"`;
13
+ return `"${value.replaceAll('"', '""')}"`;
14
14
  }
15
15
  placeholder(_index) {
16
16
  return "?";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunnykit/orm",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
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",