@bunnykit/orm 0.1.23 → 0.1.24

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,7 +5,7 @@ import { BelongsToMany } from "./BelongsToMany.js";
5
5
  export type ModelConstructor<T extends Model = Model> = (new (...args: any[]) => T) & Omit<typeof Model, "prototype">;
6
6
  export type GlobalScope = (builder: Builder<any>, model: ModelConstructor) => void;
7
7
  export type LiteralUnion<T extends string> = T | (string & {});
8
- type BaseModelInstanceKey = "$attributes" | "$original" | "$exists" | "$relations" | "$casts" | "$connection" | "fill" | "setConnection" | "getConnection" | "isFillable" | "getAttribute" | "setAttribute" | "castAttribute" | "serializeCastAttribute" | "mergeCasts" | "getDirty" | "isDirty" | "save" | "updateTimestamps" | "touch" | "increment" | "decrement" | "load" | "delete" | "restore" | "forceDelete" | "refresh" | "toJSON" | "toString" | "freshTimestamp" | "setRelation" | "getRelation" | "hasMany" | "belongsTo" | "hasOne" | "hasManyThrough" | "hasOneThrough" | "belongsToMany" | "morphTo" | "morphOne" | "morphMany" | "morphToMany" | "morphedByMany";
8
+ type BaseModelInstanceKey = "$attributes" | "$original" | "$exists" | "$relations" | "$casts" | "$castCache" | "$connection" | "fill" | "setConnection" | "getConnection" | "isFillable" | "getAttribute" | "setAttribute" | "castAttribute" | "serializeCastAttribute" | "mergeCasts" | "getDirty" | "isDirty" | "save" | "updateTimestamps" | "touch" | "increment" | "decrement" | "load" | "delete" | "restore" | "forceDelete" | "refresh" | "toJSON" | "toString" | "freshTimestamp" | "setRelation" | "getRelation" | "hasMany" | "belongsTo" | "hasOne" | "hasManyThrough" | "hasOneThrough" | "belongsToMany" | "morphTo" | "morphOne" | "morphMany" | "morphToMany" | "morphedByMany";
9
9
  export type ModelInstanceAttributeKeys<T> = Extract<Exclude<keyof T, BaseModelInstanceKey>, string>;
10
10
  export type ModelAttributes<T> = T extends {
11
11
  $attributes: Record<string, any>;
@@ -111,10 +111,9 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
111
111
  $exists: boolean;
112
112
  $relations: Record<string, any>;
113
113
  $casts: Record<string, CastDefinition>;
114
+ $castCache: Record<string, any>;
114
115
  $connection?: Connection;
115
116
  constructor(attributes?: Partial<T>);
116
- private defineAttributeProperty;
117
- private syncAttributeProperties;
118
117
  static getTable(): string;
119
118
  static getConnection(): Connection;
120
119
  static setConnection(connection: Connection): void;
@@ -127,6 +126,7 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
127
126
  static applyGlobalScopes(builder: Builder<any>): void;
128
127
  static getQualifiedDeletedAtColumn(): string;
129
128
  static shouldAutoGeneratePrimaryKey(): Promise<boolean>;
129
+ static hydrate<M extends ModelConstructor>(this: M, row: Record<string, any>, connection?: Connection): InstanceType<M>;
130
130
  static create<M extends ModelConstructor>(this: M, attributes: ModelAttributeInput<InstanceType<M>>): Promise<InstanceType<M>>;
131
131
  static find<M extends ModelConstructor>(this: M, id: any): Promise<InstanceType<M> | null>;
132
132
  static findOrFail<M extends ModelConstructor>(this: M, id: any): Promise<InstanceType<M>>;
@@ -8,6 +8,44 @@ import { ModelNotFoundError } from "./ModelNotFoundError.js";
8
8
  import { ConnectionManager } from "../connection/ConnectionManager.js";
9
9
  import { TenantContext } from "../connection/TenantContext.js";
10
10
  import { IdentityMap } from "./IdentityMap.js";
11
+ const modelProxyHandler = {
12
+ get(target, prop, receiver) {
13
+ if (typeof prop === "string" && !(prop in target) && prop in target.$attributes) {
14
+ return target.getAttribute(prop);
15
+ }
16
+ return Reflect.get(target, prop, receiver);
17
+ },
18
+ set(target, prop, value, receiver) {
19
+ if (typeof prop === "string" && !prop.startsWith("$") && !(prop in target)) {
20
+ target.setAttribute(prop, value);
21
+ return true;
22
+ }
23
+ return Reflect.set(target, prop, value, receiver);
24
+ },
25
+ has(target, prop) {
26
+ if (typeof prop === "string" && prop in target.$attributes)
27
+ return true;
28
+ return Reflect.has(target, prop);
29
+ },
30
+ getOwnPropertyDescriptor(target, prop) {
31
+ if (typeof prop === "string" && prop in target.$attributes) {
32
+ return {
33
+ enumerable: true,
34
+ configurable: true,
35
+ value: target.getAttribute(prop),
36
+ };
37
+ }
38
+ return Reflect.getOwnPropertyDescriptor(target, prop);
39
+ },
40
+ ownKeys(target) {
41
+ const keys = new Set(Reflect.ownKeys(target));
42
+ for (const key of Object.keys(target.$attributes)) {
43
+ if (!key.startsWith("$"))
44
+ keys.add(key);
45
+ }
46
+ return Array.from(keys);
47
+ },
48
+ };
11
49
  const globalScopes = new WeakMap();
12
50
  function getGlobalScopes(model) {
13
51
  const scopes = new Map();
@@ -306,6 +344,7 @@ export class Model {
306
344
  $exists = false;
307
345
  $relations = {};
308
346
  $casts = {};
347
+ $castCache = {};
309
348
  $connection;
310
349
  constructor(attributes) {
311
350
  const defaults = this.constructor.attributes;
@@ -315,72 +354,7 @@ export class Model {
315
354
  if (attributes) {
316
355
  this.fill(attributes);
317
356
  }
318
- this.syncAttributeProperties();
319
- // Minimal Proxy fallback for dynamic property access on undefined keys.
320
- // Pre-defined attribute getters/setters bypass the Proxy entirely.
321
- return new Proxy(this, {
322
- get(target, prop, receiver) {
323
- if (typeof prop === "string" && !(prop in target) && prop in target.$attributes) {
324
- return target.getAttribute(prop);
325
- }
326
- return Reflect.get(target, prop, receiver);
327
- },
328
- set(target, prop, value, receiver) {
329
- if (typeof prop === "string" && !prop.startsWith("$") && !(prop in target)) {
330
- target.setAttribute(prop, value);
331
- return true;
332
- }
333
- return Reflect.set(target, prop, value, receiver);
334
- },
335
- has(target, prop) {
336
- if (typeof prop === "string" && prop in target.$attributes)
337
- return true;
338
- return Reflect.has(target, prop);
339
- },
340
- getOwnPropertyDescriptor(target, prop) {
341
- if (typeof prop === "string" && prop in target.$attributes) {
342
- return {
343
- enumerable: true,
344
- configurable: true,
345
- value: target.getAttribute(prop),
346
- };
347
- }
348
- return Reflect.getOwnPropertyDescriptor(target, prop);
349
- },
350
- ownKeys(target) {
351
- const keys = new Set(Reflect.ownKeys(target));
352
- for (const key of Object.keys(target.$attributes)) {
353
- if (!key.startsWith("$"))
354
- keys.add(key);
355
- }
356
- return Array.from(keys);
357
- },
358
- });
359
- }
360
- defineAttributeProperty(key) {
361
- if (key in this)
362
- return;
363
- Object.defineProperty(this, key, {
364
- get: () => this.getAttribute(key),
365
- set: (value) => this.setAttribute(key, value),
366
- enumerable: true,
367
- configurable: true,
368
- });
369
- }
370
- syncAttributeProperties() {
371
- const currentKeys = new Set(Object.keys(this.$attributes));
372
- // Remove stale attribute properties
373
- for (const key of Reflect.ownKeys(this)) {
374
- if (key.startsWith("$") || typeof key !== "string")
375
- continue;
376
- const desc = Object.getOwnPropertyDescriptor(this, key);
377
- if (desc && desc.get && desc.configurable && !currentKeys.has(key)) {
378
- delete this[key];
379
- }
380
- }
381
- for (const key of currentKeys) {
382
- this.defineAttributeProperty(key);
383
- }
357
+ return new Proxy(this, modelProxyHandler);
384
358
  }
385
359
  static getTable() {
386
360
  return this.table || snakeCase(this.name) + "s";
@@ -459,6 +433,17 @@ export class Model {
459
433
  const numericTypes = new Set(["integer", "int", "bigint", "smallint", "tinyint", "real", "float", "double", "decimal", "numeric"]);
460
434
  return !numericTypes.has(type);
461
435
  }
436
+ static hydrate(row, connection) {
437
+ const instance = new this();
438
+ instance.$attributes = { ...instance.$attributes, ...row };
439
+ instance.$original = { ...row };
440
+ instance.$castCache = {};
441
+ instance.$exists = true;
442
+ if (connection) {
443
+ instance.setConnection(connection);
444
+ }
445
+ return instance;
446
+ }
462
447
  static async create(attributes) {
463
448
  const instance = new this();
464
449
  instance.fill(attributes);
@@ -701,12 +686,19 @@ export class Model {
701
686
  return true;
702
687
  }
703
688
  getAttribute(key) {
689
+ if (Object.prototype.hasOwnProperty.call(this.$castCache, key)) {
690
+ return this.$castCache[key];
691
+ }
704
692
  const value = this.$attributes[key];
705
- return this.castAttribute(key, value);
693
+ const casted = this.castAttribute(key, value);
694
+ if (this.getCastDefinition(key) && value !== null && value !== undefined) {
695
+ this.$castCache[key] = casted;
696
+ }
697
+ return casted;
706
698
  }
707
699
  setAttribute(key, value) {
708
700
  this.$attributes[key] = this.serializeCastAttribute(key, value);
709
- this.defineAttributeProperty(key);
701
+ delete this.$castCache[key];
710
702
  }
711
703
  castAttribute(key, value) {
712
704
  const cast = this.getCastDefinition(key);
@@ -785,6 +777,7 @@ export class Model {
785
777
  }
786
778
  mergeCasts(casts) {
787
779
  this.$casts = { ...this.$casts, ...casts };
780
+ this.$castCache = {};
788
781
  return this;
789
782
  }
790
783
  getCastDefinition(key) {
@@ -819,6 +812,7 @@ export class Model {
819
812
  let dirty = this.getDirty();
820
813
  if (Object.keys(dirty).length > 0 && constructor.timestamps) {
821
814
  this.$attributes["updated_at"] = this.freshTimestamp();
815
+ delete this.$castCache.updated_at;
822
816
  dirty = this.getDirty();
823
817
  }
824
818
  if (Object.keys(dirty).length > 0) {
@@ -840,6 +834,8 @@ export class Model {
840
834
  const now = this.freshTimestamp();
841
835
  this.$attributes["created_at"] = now;
842
836
  this.$attributes["updated_at"] = now;
837
+ delete this.$castCache.created_at;
838
+ delete this.$castCache.updated_at;
843
839
  }
844
840
  const primaryKey = constructor.primaryKey;
845
841
  const primaryKeyValue = this.getAttribute(primaryKey);
@@ -847,6 +843,7 @@ export class Model {
847
843
  if ((primaryKeyValue === null || primaryKeyValue === undefined || primaryKeyValue === "") && shouldGeneratePrimaryKey) {
848
844
  const generated = crypto.randomUUID();
849
845
  this.$attributes[primaryKey] = generated;
846
+ delete this.$castCache[primaryKey];
850
847
  }
851
848
  const connection = this.getConnection();
852
849
  if (shouldGeneratePrimaryKey || primaryKeyValue !== null && primaryKeyValue !== undefined && primaryKeyValue !== "") {
@@ -856,6 +853,7 @@ export class Model {
856
853
  const result = await new Builder(connection, connection.qualifyTable(constructor.getTable())).insertGetId(this.$attributes);
857
854
  if (result) {
858
855
  this.$attributes[constructor.primaryKey] = result;
856
+ delete this.$castCache[constructor.primaryKey];
859
857
  }
860
858
  }
861
859
  this.$exists = true;
@@ -863,7 +861,6 @@ export class Model {
863
861
  await ObserverRegistry.dispatch("created", this);
864
862
  await ObserverRegistry.dispatch("saved", this);
865
863
  }
866
- this.syncAttributeProperties();
867
864
  const identityMap = IdentityMap.current();
868
865
  if (identityMap) {
869
866
  const pk = this.getAttribute(constructor.primaryKey);
@@ -879,10 +876,11 @@ export class Model {
879
876
  return;
880
877
  const now = this.freshTimestamp();
881
878
  this.$attributes["updated_at"] = now;
879
+ delete this.$castCache.updated_at;
882
880
  if (!this.$exists) {
883
881
  this.$attributes["created_at"] = now;
882
+ delete this.$castCache.created_at;
884
883
  }
885
- this.syncAttributeProperties();
886
884
  }
887
885
  async touch() {
888
886
  if (!this.$exists)
@@ -897,8 +895,8 @@ export class Model {
897
895
  .where(constructor.primaryKey, pk)
898
896
  .update({ updated_at: now });
899
897
  this.$attributes["updated_at"] = now;
898
+ delete this.$castCache.updated_at;
900
899
  this.$original = { ...this.$attributes };
901
- this.syncAttributeProperties();
902
900
  return true;
903
901
  }
904
902
  async increment(column, amount = 1, extra = {}) {
@@ -914,11 +912,12 @@ export class Model {
914
912
  }
915
913
  await builder.increment(column, amount, extra);
916
914
  this.$attributes[column] = (this.$attributes[column] || 0) + amount;
915
+ delete this.$castCache[column];
917
916
  for (const [key, value] of Object.entries(extra)) {
918
917
  this.$attributes[key] = value;
918
+ delete this.$castCache[key];
919
919
  }
920
920
  this.$original = { ...this.$attributes };
921
- this.syncAttributeProperties();
922
921
  return this;
923
922
  }
924
923
  async decrement(column, amount = 1, extra = {}) {
@@ -942,8 +941,8 @@ export class Model {
942
941
  .where(constructor.primaryKey, pk)
943
942
  .update({ [constructor.deletedAtColumn]: deletedAt });
944
943
  this.$attributes[constructor.deletedAtColumn] = deletedAt;
944
+ delete this.$castCache[constructor.deletedAtColumn];
945
945
  this.$original = { ...this.$attributes };
946
- this.syncAttributeProperties();
947
946
  }
948
947
  else {
949
948
  const connection = this.getConnection();
@@ -971,9 +970,9 @@ export class Model {
971
970
  .where(constructor.primaryKey, pk)
972
971
  .update({ [constructor.deletedAtColumn]: null });
973
972
  this.$attributes[constructor.deletedAtColumn] = null;
973
+ delete this.$castCache[constructor.deletedAtColumn];
974
974
  this.$original = { ...this.$attributes };
975
975
  this.$exists = true;
976
- this.syncAttributeProperties();
977
976
  return true;
978
977
  }
979
978
  async forceDelete() {
@@ -1006,7 +1005,7 @@ export class Model {
1006
1005
  if (result) {
1007
1006
  this.$attributes = result.$attributes;
1008
1007
  this.$original = { ...result.$attributes };
1009
- this.syncAttributeProperties();
1008
+ this.$castCache = {};
1010
1009
  // Ensure this instance is the canonical one in the identity map
1011
1010
  if (identityMap) {
1012
1011
  IdentityMap.set(constructor.getTable(), pk, this);
@@ -32,8 +32,10 @@ export declare class Builder<T = Record<string, any>> {
32
32
  updateJoins: string[];
33
33
  bindings: any[];
34
34
  private parameterize;
35
+ private sqlCache?;
35
36
  constructor(connection: Connection, table: string);
36
37
  private get grammar();
38
+ private invalidateSqlCache;
37
39
  setModel(model: ModelConstructor): this;
38
40
  table(table: string): this;
39
41
  select(...columns: ModelColumn<T>[]): this;
@@ -21,6 +21,7 @@ export class Builder {
21
21
  updateJoins = [];
22
22
  bindings = [];
23
23
  parameterize = false;
24
+ sqlCache;
24
25
  constructor(connection, table) {
25
26
  this.connection = connection;
26
27
  this.tableName = table;
@@ -28,19 +29,25 @@ export class Builder {
28
29
  get grammar() {
29
30
  return this.connection.getGrammar();
30
31
  }
32
+ invalidateSqlCache() {
33
+ this.sqlCache = undefined;
34
+ }
31
35
  setModel(model) {
32
36
  this.model = model;
33
37
  return this;
34
38
  }
35
39
  table(table) {
40
+ this.invalidateSqlCache();
36
41
  this.tableName = table;
37
42
  return this;
38
43
  }
39
44
  select(...columns) {
45
+ this.invalidateSqlCache();
40
46
  this.columns = columns;
41
47
  return this;
42
48
  }
43
49
  distinct() {
50
+ this.invalidateSqlCache();
44
51
  this.distinctFlag = true;
45
52
  return this;
46
53
  }
@@ -58,6 +65,7 @@ export class Builder {
58
65
  value = operator;
59
66
  operator = "=";
60
67
  }
68
+ this.invalidateSqlCache();
61
69
  this.wheres.push({ type: "basic", column, operator, value, boolean, scope });
62
70
  return this;
63
71
  }
@@ -65,6 +73,7 @@ export class Builder {
65
73
  const nested = new Builder(this.connection, this.tableName);
66
74
  callback(nested);
67
75
  if (nested.wheres.length > 0) {
76
+ this.invalidateSqlCache();
68
77
  this.wheres.push({ type: "nested", column: "", query: nested.wheres, boolean, scope: undefined });
69
78
  }
70
79
  return this;
@@ -85,26 +94,32 @@ export class Builder {
85
94
  return this.whereNot(column, value, "or");
86
95
  }
87
96
  whereIn(column, values, boolean = "and", scope) {
97
+ this.invalidateSqlCache();
88
98
  this.wheres.push({ type: "in", column, value: values, boolean, scope });
89
99
  return this;
90
100
  }
91
101
  whereNotIn(column, values, boolean = "and", scope) {
102
+ this.invalidateSqlCache();
92
103
  this.wheres.push({ type: "in", column, value: values, boolean, operator: "NOT IN", scope });
93
104
  return this;
94
105
  }
95
106
  whereNull(column, boolean = "and", scope) {
107
+ this.invalidateSqlCache();
96
108
  this.wheres.push({ type: "null", column, boolean, scope });
97
109
  return this;
98
110
  }
99
111
  whereNotNull(column, boolean = "and", scope) {
112
+ this.invalidateSqlCache();
100
113
  this.wheres.push({ type: "null", column, boolean, operator: "NOT NULL", scope });
101
114
  return this;
102
115
  }
103
116
  whereBetween(column, values, boolean = "and", scope) {
117
+ this.invalidateSqlCache();
104
118
  this.wheres.push({ type: "between", column, value: values, boolean, scope });
105
119
  return this;
106
120
  }
107
121
  whereNotBetween(column, values, boolean = "and", scope) {
122
+ this.invalidateSqlCache();
108
123
  this.wheres.push({ type: "between", column, value: values, boolean, operator: "NOT BETWEEN", scope });
109
124
  return this;
110
125
  }
@@ -139,14 +154,17 @@ export class Builder {
139
154
  return this.whereTime(column, operator, value, "or");
140
155
  }
141
156
  whereRaw(sql, boolean = "and", scope) {
157
+ this.invalidateSqlCache();
142
158
  this.wheres.push({ type: "raw", column: sql, boolean, scope });
143
159
  return this;
144
160
  }
145
161
  whereColumn(first, operator, second, boolean = "and") {
162
+ this.invalidateSqlCache();
146
163
  this.wheres.push({ type: "column", column: first, operator, value: second, boolean });
147
164
  return this;
148
165
  }
149
166
  whereExists(sql, boolean = "and", not = false) {
167
+ this.invalidateSqlCache();
150
168
  this.wheres.push({ type: "exists", column: sql, boolean, operator: not ? "NOT EXISTS" : "EXISTS" });
151
169
  return this;
152
170
  }
@@ -181,6 +199,7 @@ export class Builder {
181
199
  return this.whereRaw(sql, "or", scope);
182
200
  }
183
201
  whereJsonContains(column, value, boolean = "and", not = false) {
202
+ this.invalidateSqlCache();
184
203
  this.wheres.push({ type: "json_contains", column, value, boolean, scope: undefined, not });
185
204
  return this;
186
205
  }
@@ -189,10 +208,12 @@ export class Builder {
189
208
  value = operator;
190
209
  operator = "=";
191
210
  }
211
+ this.invalidateSqlCache();
192
212
  this.wheres.push({ type: "json_length", column, operator: String(operator), value, boolean, scope: undefined, not });
193
213
  return this;
194
214
  }
195
215
  whereLike(column, value, boolean = "and", not = false) {
216
+ this.invalidateSqlCache();
196
217
  this.wheres.push({ type: "like", column, value, boolean, scope: undefined, not });
197
218
  return this;
198
219
  }
@@ -200,23 +221,28 @@ export class Builder {
200
221
  return this.whereLike(column, value, "and", true);
201
222
  }
202
223
  whereRegexp(column, value, boolean = "and", not = false) {
224
+ this.invalidateSqlCache();
203
225
  this.wheres.push({ type: "regexp", column, value, boolean, scope: undefined, not });
204
226
  return this;
205
227
  }
206
228
  whereFullText(columns, value, boolean = "and", not = false) {
207
229
  const cols = Array.isArray(columns) ? columns : [columns];
230
+ this.invalidateSqlCache();
208
231
  this.wheres.push({ type: "fulltext", column: "", columns: cols, value, boolean, scope: undefined, not });
209
232
  return this;
210
233
  }
211
234
  whereAll(columns, operator, value, boolean = "and") {
235
+ this.invalidateSqlCache();
212
236
  this.wheres.push({ type: "all", column: "", columns: columns, operator, value, boolean, scope: undefined });
213
237
  return this;
214
238
  }
215
239
  whereAny(columns, operator, value, boolean = "and") {
240
+ this.invalidateSqlCache();
216
241
  this.wheres.push({ type: "any", column: "", columns: columns, operator, value, boolean, scope: undefined });
217
242
  return this;
218
243
  }
219
244
  orderBy(column, direction = "asc") {
245
+ this.invalidateSqlCache();
220
246
  this.orders.push({ column, direction });
221
247
  return this;
222
248
  }
@@ -227,6 +253,7 @@ export class Builder {
227
253
  return this.orderBy(column, "asc");
228
254
  }
229
255
  inRandomOrder() {
256
+ this.invalidateSqlCache();
230
257
  this.randomOrderFlag = true;
231
258
  return this;
232
259
  }
@@ -234,6 +261,7 @@ export class Builder {
234
261
  return this.orderBy(column, "desc");
235
262
  }
236
263
  reorder(column, direction = "asc") {
264
+ this.invalidateSqlCache();
237
265
  this.orders = [];
238
266
  this.randomOrderFlag = false;
239
267
  if (column) {
@@ -242,18 +270,22 @@ export class Builder {
242
270
  return this;
243
271
  }
244
272
  groupBy(...columns) {
273
+ this.invalidateSqlCache();
245
274
  this.groups.push(...columns);
246
275
  return this;
247
276
  }
248
277
  having(column, operator, value) {
278
+ this.invalidateSqlCache();
249
279
  this.havings.push({ column, operator, value, boolean: "and" });
250
280
  return this;
251
281
  }
252
282
  orHaving(column, operator, value) {
283
+ this.invalidateSqlCache();
253
284
  this.havings.push({ column, operator, value, boolean: "or" });
254
285
  return this;
255
286
  }
256
287
  havingRaw(sql, boolean = "and") {
288
+ this.invalidateSqlCache();
257
289
  this.havings.push({ sql, boolean });
258
290
  return this;
259
291
  }
@@ -261,10 +293,12 @@ export class Builder {
261
293
  return this.havingRaw(sql, "or");
262
294
  }
263
295
  limit(count) {
296
+ this.invalidateSqlCache();
264
297
  this.limitValue = count;
265
298
  return this;
266
299
  }
267
300
  offset(count) {
301
+ this.invalidateSqlCache();
268
302
  this.offsetValue = count;
269
303
  return this;
270
304
  }
@@ -273,6 +307,7 @@ export class Builder {
273
307
  }
274
308
  join(table, first, operator, second, type = "INNER") {
275
309
  const joinSql = `${type} JOIN ${this.grammar.wrap(table)} ON ${this.grammar.wrap(first)} ${operator} ${this.grammar.wrap(second)}`;
310
+ this.invalidateSqlCache();
276
311
  this.joins.push(joinSql);
277
312
  return this;
278
313
  }
@@ -283,11 +318,13 @@ export class Builder {
283
318
  return this.join(table, first, operator, second, "RIGHT");
284
319
  }
285
320
  crossJoin(table) {
321
+ this.invalidateSqlCache();
286
322
  this.joins.push(`CROSS JOIN ${this.grammar.wrap(table)}`);
287
323
  return this;
288
324
  }
289
325
  union(query, all = false) {
290
326
  const sql = typeof query === "string" ? query : query.toSql();
327
+ this.invalidateSqlCache();
291
328
  this.unions.push({ query: sql, all });
292
329
  return this;
293
330
  }
@@ -299,10 +336,12 @@ export class Builder {
299
336
  return this;
300
337
  }
301
338
  withoutGlobalScope(scope) {
339
+ this.invalidateSqlCache();
302
340
  this.wheres = this.wheres.filter((where) => where.scope !== scope);
303
341
  return this;
304
342
  }
305
343
  withoutGlobalScopes() {
344
+ this.invalidateSqlCache();
306
345
  this.wheres = this.wheres.filter((where) => !where.scope);
307
346
  return this;
308
347
  }
@@ -407,6 +446,7 @@ export class Builder {
407
446
  return this.withAggregate(relationName, column, "MAX", alias);
408
447
  }
409
448
  addSelect(...columns) {
449
+ this.invalidateSqlCache();
410
450
  if (this.columns.length === 1 && this.columns[0] === "*") {
411
451
  this.columns = [`${this.tableName}.*`];
412
452
  }
@@ -414,15 +454,18 @@ export class Builder {
414
454
  return this;
415
455
  }
416
456
  selectRaw(sql) {
457
+ this.invalidateSqlCache();
417
458
  this.columns.push(sql);
418
459
  return this;
419
460
  }
420
461
  fromSub(query, as) {
421
462
  const sql = typeof query === "string" ? query : query.toSql();
463
+ this.invalidateSqlCache();
422
464
  this.fromRaw = `(${sql}) AS ${this.grammar.wrap(as)}`;
423
465
  return this;
424
466
  }
425
467
  updateFrom(table, first, operator, second) {
468
+ this.invalidateSqlCache();
426
469
  this.updateJoins.push(`INNER JOIN ${this.grammar.wrap(table)} ON ${this.grammar.wrap(first)} ${operator} ${this.grammar.wrap(second)}`);
427
470
  return this;
428
471
  }
@@ -598,6 +641,8 @@ export class Builder {
598
641
  return column.includes("(") || /\s+as\s+/i.test(column) || /^[0-9]+$/.test(column);
599
642
  }
600
643
  toSql() {
644
+ if (!this.parameterize && this.sqlCache)
645
+ return this.sqlCache;
601
646
  const distinct = this.distinctFlag ? "DISTINCT " : "";
602
647
  const from = this.fromRaw || this.grammar.wrap(this.tableName);
603
648
  let sql = `SELECT ${distinct}${this.compileColumns()} FROM ${from}`;
@@ -613,7 +658,10 @@ export class Builder {
613
658
  for (const union of this.unions) {
614
659
  sql += ` UNION${union.all ? " ALL" : ""} ${union.query}`;
615
660
  }
616
- return sql.replace(/\s+/g, " ").trim();
661
+ const compiled = sql.replace(/\s+/g, " ").trim();
662
+ if (!this.parameterize)
663
+ this.sqlCache = compiled;
664
+ return compiled;
617
665
  }
618
666
  async get() {
619
667
  this.bindings = [];
@@ -636,12 +684,7 @@ export class Builder {
636
684
  }
637
685
  }
638
686
  }
639
- const instance = new this.model(row);
640
- instance.$exists = true;
641
- instance.$original = { ...row };
642
- if (typeof instance.setConnection === "function") {
643
- instance.setConnection(this.connection);
644
- }
687
+ const instance = this.model.hydrate(row, this.connection);
645
688
  if (identityMap) {
646
689
  const pk = row[primaryKey];
647
690
  if (pk !== null && pk !== undefined) {
@@ -725,6 +768,7 @@ export class Builder {
725
768
  query.offsetValue = undefined;
726
769
  query.eagerLoads = [];
727
770
  query.lockMode = undefined;
771
+ query.invalidateSqlCache();
728
772
  const result = await query.first();
729
773
  return result ? result[alias] : null;
730
774
  }
@@ -748,6 +792,7 @@ export class Builder {
748
792
  countQuery.limitValue = undefined;
749
793
  countQuery.offsetValue = undefined;
750
794
  countQuery.orders = [];
795
+ countQuery.invalidateSqlCache();
751
796
  const total = await countQuery.count();
752
797
  const data = await this.clone().forPage(page, perPage).get();
753
798
  return {
@@ -1023,6 +1068,7 @@ export class Builder {
1023
1068
  lockForUpdate() {
1024
1069
  const driver = this.connection.getDriverName();
1025
1070
  if (driver !== "sqlite") {
1071
+ this.invalidateSqlCache();
1026
1072
  this.lockMode = "FOR UPDATE";
1027
1073
  }
1028
1074
  return this;
@@ -1030,21 +1076,25 @@ export class Builder {
1030
1076
  sharedLock() {
1031
1077
  const driver = this.connection.getDriverName();
1032
1078
  if (driver === "mysql") {
1079
+ this.invalidateSqlCache();
1033
1080
  this.lockMode = "LOCK IN SHARE MODE";
1034
1081
  }
1035
1082
  else if (driver === "postgres") {
1083
+ this.invalidateSqlCache();
1036
1084
  this.lockMode = "FOR SHARE";
1037
1085
  }
1038
1086
  return this;
1039
1087
  }
1040
1088
  skipLocked() {
1041
1089
  if (this.lockMode) {
1090
+ this.invalidateSqlCache();
1042
1091
  this.lockMode += " SKIP LOCKED";
1043
1092
  }
1044
1093
  return this;
1045
1094
  }
1046
1095
  noWait() {
1047
1096
  if (this.lockMode) {
1097
+ this.invalidateSqlCache();
1048
1098
  this.lockMode += " NOWAIT";
1049
1099
  }
1050
1100
  return this;
@@ -1054,6 +1104,7 @@ export class Builder {
1054
1104
  value = operator;
1055
1105
  operator = "=";
1056
1106
  }
1107
+ this.invalidateSqlCache();
1057
1108
  this.wheres.push({ type: "date", column: column, operator, value, boolean, scope: undefined, dateType: type });
1058
1109
  return this;
1059
1110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunnykit/orm",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
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",