@bunnykit/orm 0.1.5 → 0.1.7

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.
@@ -5,6 +5,8 @@ import { MorphTo, MorphOne, MorphMany, MorphToMany } from "./MorphRelations.js";
5
5
  import { BelongsToMany } from "./BelongsToMany.js";
6
6
  import { Schema } from "../schema/Schema.js";
7
7
  import { ModelNotFoundError } from "./ModelNotFoundError.js";
8
+ import { ConnectionManager } from "../connection/ConnectionManager.js";
9
+ import { TenantContext } from "../connection/TenantContext.js";
8
10
  const globalScopes = new WeakMap();
9
11
  function getGlobalScopes(model) {
10
12
  const scopes = new Map();
@@ -32,7 +34,7 @@ export class Relation {
32
34
  constructor(parent, related, foreignKey, localKey) {
33
35
  this.parent = parent;
34
36
  this.related = related;
35
- this.builder = related.query();
37
+ this.builder = related.on(parent.getConnection());
36
38
  this.foreignKey = foreignKey || this.defaultForeignKey();
37
39
  this.localKey = localKey || related.primaryKey;
38
40
  }
@@ -46,7 +48,7 @@ export class Relation {
46
48
  return column.includes(".") ? column : `${this.related.getTable()}.${column}`;
47
49
  }
48
50
  newExistenceQuery(parentQuery, aggregate, callback) {
49
- const query = this.related.query().select(aggregate);
51
+ const query = this.related.on(parentQuery.connection).select(aggregate);
50
52
  query.whereColumn(`${this.related.getTable()}.${this.foreignKey}`, "=", `${parentQuery.tableName}.${this.localKey}`);
51
53
  if (callback)
52
54
  callback(query);
@@ -74,7 +76,7 @@ export class HasMany extends Relation {
74
76
  this.builder.where(this.foreignKey, parentValue);
75
77
  }
76
78
  addEagerConstraints(models) {
77
- this.builder = this.related.query();
79
+ this.builder = this.related.on(this.parent.getConnection());
78
80
  const keys = models.map((m) => m.getAttribute(this.localKey));
79
81
  this.builder.whereIn(this.foreignKey, keys);
80
82
  }
@@ -121,7 +123,7 @@ export class BelongsTo extends Relation {
121
123
  this.builder.where(this.localKey, childValue);
122
124
  }
123
125
  addEagerConstraints(models) {
124
- this.builder = this.related.query();
126
+ this.builder = this.related.on(this.parent.getConnection());
125
127
  const keys = models.map((m) => m.getAttribute(this.foreignKey));
126
128
  this.builder.whereIn(this.localKey, keys);
127
129
  }
@@ -152,7 +154,7 @@ export class BelongsTo extends Relation {
152
154
  return this.parent;
153
155
  }
154
156
  newExistenceQuery(parentQuery, aggregate, callback) {
155
- const query = this.related.query().select(aggregate);
157
+ const query = this.related.on(parentQuery.connection).select(aggregate);
156
158
  query.whereColumn(`${this.related.getTable()}.${this.localKey}`, "=", `${parentQuery.tableName}.${this.foreignKey}`);
157
159
  if (callback)
158
160
  callback(query);
@@ -184,7 +186,7 @@ export class HasManyThrough extends Relation {
184
186
  const throughTable = this.through.getTable();
185
187
  const relatedTable = this.related.getTable();
186
188
  const keys = models.map((m) => m.getAttribute(this.localKey));
187
- this.builder = this.related.query();
189
+ this.builder = this.related.on(this.parent.getConnection());
188
190
  this.builder.select(`${relatedTable}.*`, `${throughTable}.${this.firstKey}`);
189
191
  this.builder.join(throughTable, `${throughTable}.${this.secondLocalKey}`, "=", `${relatedTable}.${this.secondKey}`);
190
192
  this.builder.whereIn(`${throughTable}.${this.firstKey}`, keys);
@@ -212,7 +214,7 @@ export class HasManyThrough extends Relation {
212
214
  newExistenceQuery(parentQuery, aggregate, callback) {
213
215
  const throughTable = this.through.getTable();
214
216
  const relatedTable = this.related.getTable();
215
- const query = this.related.query().select(aggregate);
217
+ const query = this.related.on(parentQuery.connection).select(aggregate);
216
218
  query.join(throughTable, `${throughTable}.${this.secondLocalKey}`, "=", `${relatedTable}.${this.secondKey}`);
217
219
  query.whereColumn(`${throughTable}.${this.firstKey}`, "=", `${parentQuery.tableName}.${this.localKey}`);
218
220
  if (callback)
@@ -250,7 +252,7 @@ export class HasOne extends Relation {
250
252
  this.builder.where(this.foreignKey, parentValue);
251
253
  }
252
254
  addEagerConstraints(models) {
253
- this.builder = this.related.query();
255
+ this.builder = this.related.on(this.parent.getConnection());
254
256
  const keys = models.map((m) => m.getAttribute(this.localKey));
255
257
  this.builder.whereIn(this.foreignKey, keys);
256
258
  }
@@ -293,6 +295,7 @@ export class Model {
293
295
  $exists = false;
294
296
  $relations = {};
295
297
  $casts = {};
298
+ $connection;
296
299
  constructor(attributes) {
297
300
  const defaults = this.constructor.attributes;
298
301
  if (Object.keys(defaults).length > 0) {
@@ -338,16 +341,35 @@ export class Model {
338
341
  return this.table || snakeCase(this.name) + "s";
339
342
  }
340
343
  static getConnection() {
341
- if (!this.connection) {
344
+ const tenantConnection = TenantContext.current()?.connection;
345
+ const ownConnection = Object.prototype.hasOwnProperty.call(this, "connection") ? this.connection : undefined;
346
+ const connection = ownConnection || tenantConnection || this.connection || ConnectionManager.getDefault();
347
+ if (!connection) {
342
348
  throw new Error(`No connection set on model ${this.name}`);
343
349
  }
344
- return this.connection;
350
+ return connection;
345
351
  }
346
352
  static setConnection(connection) {
347
353
  this.connection = connection;
354
+ ConnectionManager.setDefault(connection);
355
+ }
356
+ static on(connection) {
357
+ const resolved = typeof connection === "string" ? ConnectionManager.require(connection) : connection;
358
+ const builder = new Builder(resolved, resolved.qualifyTable(this.getTable()));
359
+ builder.setModel(this);
360
+ this.applyGlobalScopes(builder);
361
+ return builder;
362
+ }
363
+ static forTenant(tenantId) {
364
+ const context = ConnectionManager.getResolvedTenant(tenantId);
365
+ if (!context) {
366
+ throw new Error(`Tenant "${tenantId}" has not been resolved. Use TenantContext.run() or await ConnectionManager.resolveTenant() first.`);
367
+ }
368
+ return this.on(context.connection);
348
369
  }
349
370
  static query() {
350
- const builder = new Builder(this.getConnection(), this.getTable());
371
+ const connection = this.getConnection();
372
+ const builder = new Builder(connection, connection.qualifyTable(this.getTable()));
351
373
  builder.setModel(this);
352
374
  this.applyGlobalScopes(builder);
353
375
  return builder;
@@ -613,6 +635,13 @@ export class Model {
613
635
  }
614
636
  return this;
615
637
  }
638
+ setConnection(connection) {
639
+ this.$connection = connection;
640
+ return this;
641
+ }
642
+ getConnection() {
643
+ return this.$connection || this.constructor.getConnection();
644
+ }
616
645
  isFillable(key) {
617
646
  const constructor = this.constructor;
618
647
  if (constructor.fillable.length > 0) {
@@ -745,7 +774,8 @@ export class Model {
745
774
  const dirty = this.getDirty();
746
775
  if (Object.keys(dirty).length > 0) {
747
776
  const pk = this.getAttribute(constructor.primaryKey);
748
- await new Builder(constructor.getConnection(), constructor.getTable())
777
+ const connection = this.getConnection();
778
+ await new Builder(connection, connection.qualifyTable(constructor.getTable()))
749
779
  .where(constructor.primaryKey, pk)
750
780
  .update(dirty);
751
781
  }
@@ -768,11 +798,12 @@ export class Model {
768
798
  const generated = crypto.randomUUID();
769
799
  this.$attributes[primaryKey] = generated;
770
800
  }
801
+ const connection = this.getConnection();
771
802
  if (shouldGeneratePrimaryKey || primaryKeyValue !== null && primaryKeyValue !== undefined && primaryKeyValue !== "") {
772
- await new Builder(constructor.getConnection(), constructor.getTable()).insert(this.$attributes);
803
+ await new Builder(connection, connection.qualifyTable(constructor.getTable())).insert(this.$attributes);
773
804
  }
774
805
  else {
775
- const result = await new Builder(constructor.getConnection(), constructor.getTable()).insertGetId(this.$attributes);
806
+ const result = await new Builder(connection, connection.qualifyTable(constructor.getTable())).insertGetId(this.$attributes);
776
807
  if (result) {
777
808
  this.$attributes[constructor.primaryKey] = result;
778
809
  }
@@ -802,7 +833,8 @@ export class Model {
802
833
  return false;
803
834
  const now = this.freshTimestamp();
804
835
  const pk = this.getAttribute(constructor.primaryKey);
805
- await new Builder(constructor.getConnection(), constructor.getTable())
836
+ const connection = this.getConnection();
837
+ await new Builder(connection, connection.qualifyTable(constructor.getTable()))
806
838
  .where(constructor.primaryKey, pk)
807
839
  .update({ updated_at: now });
808
840
  this.$attributes["updated_at"] = now;
@@ -814,7 +846,8 @@ export class Model {
814
846
  const pk = this.getAttribute(constructor.primaryKey);
815
847
  if (!pk)
816
848
  return this;
817
- const builder = new Builder(constructor.getConnection(), constructor.getTable())
849
+ const connection = this.getConnection();
850
+ const builder = new Builder(connection, connection.qualifyTable(constructor.getTable()))
818
851
  .where(constructor.primaryKey, pk);
819
852
  if (constructor.timestamps) {
820
853
  extra = { ...extra, updated_at: this.freshTimestamp() };
@@ -843,14 +876,16 @@ export class Model {
843
876
  return false;
844
877
  if (constructor.softDeletes) {
845
878
  const deletedAt = this.freshTimestamp();
846
- await new Builder(constructor.getConnection(), constructor.getTable())
879
+ const connection = this.getConnection();
880
+ await new Builder(connection, connection.qualifyTable(constructor.getTable()))
847
881
  .where(constructor.primaryKey, pk)
848
882
  .update({ [constructor.deletedAtColumn]: deletedAt });
849
883
  this.$attributes[constructor.deletedAtColumn] = deletedAt;
850
884
  this.$original = { ...this.$attributes };
851
885
  }
852
886
  else {
853
- await new Builder(constructor.getConnection(), constructor.getTable())
887
+ const connection = this.getConnection();
888
+ await new Builder(connection, connection.qualifyTable(constructor.getTable()))
854
889
  .where(constructor.primaryKey, pk)
855
890
  .delete();
856
891
  this.$exists = false;
@@ -865,7 +900,8 @@ export class Model {
865
900
  const pk = this.getAttribute(constructor.primaryKey);
866
901
  if (!pk)
867
902
  return false;
868
- await new Builder(constructor.getConnection(), constructor.getTable())
903
+ const connection = this.getConnection();
904
+ await new Builder(connection, connection.qualifyTable(constructor.getTable()))
869
905
  .where(constructor.primaryKey, pk)
870
906
  .update({ [constructor.deletedAtColumn]: null });
871
907
  this.$attributes[constructor.deletedAtColumn] = null;
@@ -878,7 +914,8 @@ export class Model {
878
914
  const pk = this.getAttribute(constructor.primaryKey);
879
915
  if (!pk)
880
916
  return false;
881
- await new Builder(constructor.getConnection(), constructor.getTable())
917
+ const connection = this.getConnection();
918
+ await new Builder(connection, connection.qualifyTable(constructor.getTable()))
882
919
  .where(constructor.primaryKey, pk)
883
920
  .delete();
884
921
  this.$exists = false;
@@ -31,7 +31,7 @@ export class MorphTo {
31
31
  if (!Related) {
32
32
  throw new Error(`No morph mapping found for type: ${type}. Register it with MorphMap.register() or pass a typeMap.`);
33
33
  }
34
- return Related.find(id);
34
+ return Related.on(this.parent.getConnection()).find(id);
35
35
  }
36
36
  addEagerConstraints(models) {
37
37
  // MorphTo eager loading is handled separately in getEager
@@ -83,7 +83,7 @@ export class MorphOne {
83
83
  this.typeColumn = typeColumn || `${name}_type`;
84
84
  this.idColumn = idColumn || `${name}_id`;
85
85
  this.localKey = localKey || parent.constructor.primaryKey;
86
- this.builder = related.query();
86
+ this.builder = related.on(parent.getConnection());
87
87
  this.builder.where(this.typeColumn, this.getMorphType());
88
88
  this.builder.where(this.idColumn, this.parent.getAttribute(this.localKey));
89
89
  }
@@ -94,7 +94,7 @@ export class MorphOne {
94
94
  return this.builder;
95
95
  }
96
96
  addEagerConstraints(models) {
97
- this.builder = this.related.query();
97
+ this.builder = this.related.on(this.parent.getConnection());
98
98
  const keys = models.map((m) => m.getAttribute(this.localKey));
99
99
  this.builder.whereIn(this.idColumn, keys);
100
100
  this.builder.where(this.typeColumn, this.getMorphType());
@@ -120,7 +120,7 @@ export class MorphOne {
120
120
  return column.includes(".") ? column : `${this.related.getTable()}.${column}`;
121
121
  }
122
122
  newExistenceQuery(parentTable, aggregate, callback) {
123
- const query = this.related.query().select(aggregate);
123
+ const query = this.related.on(this.parent.getConnection()).select(aggregate);
124
124
  query.whereColumn(`${this.related.getTable()}.${this.idColumn}`, "=", `${parentTable}.${this.localKey}`);
125
125
  query.where(`${this.related.getTable()}.${this.typeColumn}`, this.getMorphType());
126
126
  if (callback)
@@ -152,7 +152,7 @@ export class MorphMany {
152
152
  this.typeColumn = typeColumn || `${name}_type`;
153
153
  this.idColumn = idColumn || `${name}_id`;
154
154
  this.localKey = localKey || parent.constructor.primaryKey;
155
- this.builder = related.query();
155
+ this.builder = related.on(parent.getConnection());
156
156
  this.builder.where(this.typeColumn, this.getMorphType());
157
157
  this.builder.where(this.idColumn, this.parent.getAttribute(this.localKey));
158
158
  }
@@ -163,7 +163,7 @@ export class MorphMany {
163
163
  return this.builder;
164
164
  }
165
165
  addEagerConstraints(models) {
166
- this.builder = this.related.query();
166
+ this.builder = this.related.on(this.parent.getConnection());
167
167
  const keys = models.map((m) => m.getAttribute(this.localKey));
168
168
  this.builder.whereIn(this.idColumn, keys);
169
169
  this.builder.where(this.typeColumn, this.getMorphType());
@@ -191,7 +191,7 @@ export class MorphMany {
191
191
  return column.includes(".") ? column : `${this.related.getTable()}.${column}`;
192
192
  }
193
193
  newExistenceQuery(parentTable, aggregate, callback) {
194
- const query = this.related.query().select(aggregate);
194
+ const query = this.related.on(this.parent.getConnection()).select(aggregate);
195
195
  query.whereColumn(`${this.related.getTable()}.${this.idColumn}`, "=", `${parentTable}.${this.localKey}`);
196
196
  query.where(`${this.related.getTable()}.${this.typeColumn}`, this.getMorphType());
197
197
  if (callback)
@@ -229,7 +229,7 @@ export class MorphToMany {
229
229
  this.morphType = morphType || parent.constructor.morphName || parent.constructor.name;
230
230
  this.foreignPivotKey = foreignPivotKey || `${snakeCase(name)}_id`;
231
231
  this.relatedPivotKey = relatedPivotKey || `${snakeCase(related.name)}_id`;
232
- this.builder = related.query();
232
+ this.builder = related.on(parent.getConnection());
233
233
  this.addConstraints();
234
234
  }
235
235
  addConstraints() {
@@ -245,7 +245,7 @@ export class MorphToMany {
245
245
  addEagerConstraints(models) {
246
246
  const keys = models.map((m) => m.getAttribute(this.parentKey));
247
247
  const relatedTable = this.related.getTable();
248
- this.builder = this.related.query();
248
+ this.builder = this.related.on(this.parent.getConnection());
249
249
  this.builder.select(`${relatedTable}.*`, `${this.table}.${this.foreignPivotKey}`);
250
250
  this.builder.join(this.table, `${this.table}.${this.relatedPivotKey}`, "=", `${relatedTable}.${this.relatedKey}`);
251
251
  this.builder.whereIn(`${this.table}.${this.foreignPivotKey}`, keys);
@@ -276,7 +276,7 @@ export class MorphToMany {
276
276
  }
277
277
  newExistenceQuery(parentTable, aggregate, callback) {
278
278
  const relatedTable = this.related.getTable();
279
- const query = this.related.query().select(aggregate);
279
+ const query = this.related.on(this.parent.getConnection()).select(aggregate);
280
280
  query.join(this.table, `${this.table}.${this.relatedPivotKey}`, "=", `${relatedTable}.${this.relatedKey}`);
281
281
  query.whereColumn(`${this.table}.${this.foreignPivotKey}`, "=", `${parentTable}.${this.parentKey}`);
282
282
  query.where(`${this.table}.${this.name}_type`, this.morphType);
@@ -1,5 +1,5 @@
1
1
  import { Connection } from "../connection/Connection.js";
2
- import type { WhereClause, OrderClause } from "../types/index.js";
2
+ import type { WhereClause, OrderClause, HavingClause, UnionClause } from "../types/index.js";
3
3
  import type { Model } from "../model/Model.js";
4
4
  type RelationConstraint = (query: Builder<any>) => void | Builder<any>;
5
5
  export interface Paginator<T> {
@@ -18,7 +18,7 @@ export declare class Builder<T = Record<string, any>> {
18
18
  wheres: WhereClause[];
19
19
  orders: OrderClause[];
20
20
  groups: string[];
21
- havings: string[];
21
+ havings: HavingClause[];
22
22
  limitValue?: number;
23
23
  offsetValue?: number;
24
24
  joins: string[];
@@ -27,7 +27,11 @@ export declare class Builder<T = Record<string, any>> {
27
27
  eagerLoads: string[];
28
28
  randomOrderFlag: boolean;
29
29
  lockMode?: string;
30
+ unions: UnionClause[];
31
+ fromRaw?: string;
32
+ updateJoins: string[];
30
33
  constructor(connection: Connection, table: string);
34
+ private get grammar();
31
35
  setModel(model: typeof Model): this;
32
36
  table(table: string): this;
33
37
  select(...columns: string[]): this;
@@ -56,18 +60,44 @@ export declare class Builder<T = Record<string, any>> {
56
60
  whereRaw(sql: string, boolean?: "and" | "or", scope?: string): this;
57
61
  whereColumn(first: string, operator: string, second: string, boolean?: "and" | "or"): this;
58
62
  whereExists(sql: string, boolean?: "and" | "or", not?: boolean): this;
63
+ orWhereNull(column: string, scope?: string): this;
64
+ orWhereNotNull(column: string, scope?: string): this;
65
+ orWhereBetween(column: string, values: [any, any], scope?: string): this;
66
+ orWhereNotBetween(column: string, values: [any, any], scope?: string): this;
67
+ orWhereIn(column: string, values: any[], scope?: string): this;
68
+ orWhereNotIn(column: string, values: any[], scope?: string): this;
69
+ orWhereExists(sql: string): this;
70
+ orWhereNotExists(sql: string): this;
71
+ orWhereColumn(first: string, operator: string, second: string): this;
72
+ orWhereRaw(sql: string, scope?: string): this;
73
+ whereJsonContains(column: string, value: any, boolean?: "and" | "or", not?: boolean): this;
74
+ whereJsonLength(column: string, operator?: string | number, value?: number, boolean?: "and" | "or", not?: boolean): this;
75
+ whereLike(column: string, value: string, boolean?: "and" | "or", not?: boolean): this;
76
+ whereNotLike(column: string, value: string): this;
77
+ whereRegexp(column: string, value: string, boolean?: "and" | "or", not?: boolean): this;
78
+ whereFullText(columns: string | string[], value: string, boolean?: "and" | "or", not?: boolean): this;
79
+ whereAll(columns: string[], operator: string, value: any, boolean?: "and" | "or"): this;
80
+ whereAny(columns: string[], operator: string, value: any, boolean?: "and" | "or"): this;
59
81
  orderBy(column: string, direction?: "asc" | "desc"): this;
60
82
  latest(column?: string): this;
61
83
  oldest(column?: string): this;
62
84
  inRandomOrder(): this;
85
+ orderByDesc(column: string): this;
86
+ reorder(column?: string, direction?: "asc" | "desc"): this;
63
87
  groupBy(...columns: string[]): this;
64
88
  having(column: string, operator: string, value: any): this;
89
+ orHaving(column: string, operator: string, value: any): this;
90
+ havingRaw(sql: string, boolean?: "and" | "or"): this;
91
+ orHavingRaw(sql: string): this;
65
92
  limit(count: number): this;
66
93
  offset(count: number): this;
67
94
  forPage(page: number, perPage?: number): this;
68
95
  join(table: string, first: string, operator: string, second: string, type?: string): this;
69
96
  leftJoin(table: string, first: string, operator: string, second: string): this;
70
97
  rightJoin(table: string, first: string, operator: string, second: string): this;
98
+ crossJoin(table: string): this;
99
+ union(query: Builder<T> | string, all?: boolean): this;
100
+ unionAll(query: Builder<T> | string): this;
71
101
  with(...relations: string[]): this;
72
102
  withoutGlobalScope(scope: string): this;
73
103
  withoutGlobalScopes(): this;
@@ -89,12 +119,12 @@ export declare class Builder<T = Record<string, any>> {
89
119
  withMin(relationName: string, column: string, alias?: string): this;
90
120
  withMax(relationName: string, column: string, alias?: string): this;
91
121
  addSelect(...columns: string[]): this;
122
+ selectRaw(sql: string): this;
123
+ fromSub(query: Builder<any> | string, as: string): this;
124
+ updateFrom(table: string, first: string, operator: string, second: string): this;
92
125
  clone(): Builder<T>;
93
126
  wrapColumn(value: string): string;
94
127
  escapeValue(value: any): string;
95
- private wrap;
96
- private wrapValue;
97
- private escape;
98
128
  private compileWhereClause;
99
129
  private compileWheres;
100
130
  private compileNestedWheres;
@@ -127,6 +157,8 @@ export declare class Builder<T = Record<string, any>> {
127
157
  lazy(count?: number): AsyncGenerator<T>;
128
158
  insert(data: Partial<T> | Partial<T>[]): Promise<any>;
129
159
  insertGetId(data: Partial<T>, idColumn?: string): Promise<any>;
160
+ insertOrIgnore(data: Partial<T> | Partial<T>[]): Promise<any>;
161
+ upsert(data: Partial<T> | Partial<T>[], uniqueBy: string | string[], updateColumns?: string[]): Promise<any>;
130
162
  update(data: Partial<T>): Promise<any>;
131
163
  delete(): Promise<any>;
132
164
  increment(column: string, amount?: number, extra?: Record<string, any>): Promise<any>;
@@ -134,10 +166,17 @@ export declare class Builder<T = Record<string, any>> {
134
166
  restore(): Promise<any>;
135
167
  exists(): Promise<boolean>;
136
168
  doesntExist(): Promise<boolean>;
169
+ sole(): Promise<T>;
170
+ value(column: string): Promise<any>;
171
+ dump(): this;
172
+ dd(): never;
173
+ explain(): Promise<any[]>;
137
174
  take(count: number): this;
138
175
  skip(count: number): this;
139
176
  lockForUpdate(): this;
140
177
  sharedLock(): this;
178
+ skipLocked(): this;
179
+ noWait(): this;
141
180
  private addDateWhere;
142
181
  private getModelRelation;
143
182
  private withAggregate;