@stacksjs/orm 0.69.2 → 0.70.0

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/base.d.ts ADDED
@@ -0,0 +1,850 @@
1
+ import type { Operator } from '@stacksjs/orm';
2
+ import type { RawBuilder } from '@stacksjs/database';
3
+
4
+ export declare class BaseOrm<T, C, J> {
5
+ protected tableName: string
6
+
7
+ protected selectFromQuery: any
8
+ protected updateFromQuery: any
9
+ protected deleteFromQuery: any
10
+ protected withRelations: string[]
11
+ protected hasSelect: boolean = false
12
+
13
+ constructor(tableName: string) {
14
+ this.tableName = tableName
15
+ this.selectFromQuery = DB.instance.selectFrom(this.tableName)
16
+ this.updateFromQuery = DB.instance.updateTable(this.tableName)
17
+ this.deleteFromQuery = DB.instance.deleteFrom(this.tableName)
18
+
19
+ this.withRelations = []
20
+ }
21
+
22
+ applySelect(params: (keyof J)[] | RawBuilder<string> | string): this {
23
+ this.selectFromQuery = this.selectFromQuery.select(params)
24
+
25
+ this.hasSelect = true
26
+
27
+ return this
28
+ }
29
+
30
+ select(params: (keyof J)[] | RawBuilder<string> | string): this {
31
+ return this.applySelect(params)
32
+ }
33
+
34
+ async first(): Promise<T | undefined> {
35
+ const model = await this.applyFirst()
36
+
37
+ if (!model)
38
+ return undefined
39
+
40
+ return model as T
41
+ }
42
+
43
+ async firstOrFail(): Promise<T> {
44
+ const model = await this.applyFirstOrFail()
45
+
46
+ if (!model)
47
+ throw new HttpError(404, `No ${this.tableName} results found for query`)
48
+
49
+ return model as T
50
+ }
51
+
52
+ protected async applyFirstOrFail(): Promise<T | undefined> {
53
+ let model
54
+
55
+ if (this.hasSelect) {
56
+ model = await this.selectFromQuery.executeTakeFirst()
57
+ }
58
+ else {
59
+ model = await this.selectFromQuery.selectAll().executeTakeFirst()
60
+ }
61
+
62
+ if (!model)
63
+ throw new HttpError(404, `No ${this.tableName} results found for query`)
64
+
65
+ if (model) {
66
+ this.mapCustomGetters(model)
67
+ await this.loadRelations(model)
68
+ }
69
+
70
+ return model
71
+ }
72
+
73
+ protected async applyFind(id: number): Promise<T | undefined> {
74
+ const model = await DB.instance.selectFrom(this.tableName)
75
+ .where('id', '=', id)
76
+ .selectAll()
77
+ .executeTakeFirst()
78
+
79
+ if (!model)
80
+ return undefined
81
+
82
+ this.mapCustomGetters(model)
83
+
84
+ await this.loadRelations(model)
85
+
86
+
87
+ return model
88
+ }
89
+
90
+ async applyFindMany(ids: number[]): Promise<T[]> {
91
+ let query = DB.instance.selectFrom('users').where('id', 'in', ids)
92
+
93
+ query = query.selectAll()
94
+
95
+
96
+ const models = await query.execute()
97
+
98
+ this.mapCustomGetters(models)
99
+ await this.loadRelations(models)
100
+
101
+ return models
102
+ }
103
+
104
+ async findMany(ids: number[]): Promise<T[]> {
105
+ return await this.applyFindMany(ids)
106
+ }
107
+
108
+ async all(): Promise<T[]> {
109
+ const models = await DB.instance.selectFrom(this.tableName)
110
+ .selectAll()
111
+ .execute()
112
+
113
+ this.mapCustomGetters(models)
114
+ await this.loadRelations(models)
115
+
116
+ return models as T[]
117
+ }
118
+
119
+ async applyFirst(): Promise<T | undefined> {
120
+ let model
121
+
122
+ if (this.hasSelect) {
123
+ model = await this.selectFromQuery.executeTakeFirst()
124
+ }
125
+ else {
126
+ model = await this.selectFromQuery.selectAll().executeTakeFirst()
127
+ }
128
+
129
+ if (model) {
130
+ this.mapCustomGetters(model)
131
+ await this.loadRelations(model)
132
+ }
133
+
134
+ return model
135
+ }
136
+
137
+ applyWhere<V>(column: keyof C, ...args: [V] | [Operator, V]): this {
138
+ if (args.length === 1) {
139
+ const [value] = args
140
+ this.selectFromQuery = this.selectFromQuery.where(column, '=', value)
141
+ this.updateFromQuery = this.updateFromQuery.where(column, '=', value)
142
+ this.deleteFromQuery = this.deleteFromQuery.where(column, '=', value)
143
+ }
144
+ else {
145
+ const [operator, value] = args as [Operator, V]
146
+ this.selectFromQuery = this.selectFromQuery.where(column, operator, value)
147
+ this.updateFromQuery = this.updateFromQuery.where(column, operator, value)
148
+ this.deleteFromQuery = this.deleteFromQuery.where(column, operator, value)
149
+ }
150
+
151
+ return this
152
+ }
153
+
154
+ where<V = string>(column: keyof C, ...args: [V] | [Operator, V]): this {
155
+ return this.applyWhere<V>(column, ...args)
156
+ }
157
+
158
+ async find(id: number): Promise<T | undefined> {
159
+ return await this.applyFind(id)
160
+ }
161
+
162
+ async findOrFail(id: number): Promise<T> {
163
+ const model = await this.applyFindOrFail(id)
164
+
165
+ if (!model)
166
+ throw new HttpError(404, `No ${this.tableName} results found for id ${id}`)
167
+
168
+ return model as T
169
+ }
170
+
171
+ protected async applyFindOrFail(id: number): Promise<T | undefined> {
172
+ const model = await DB.instance.selectFrom(this.tableName)
173
+ .where('id', '=', id)
174
+ .selectAll()
175
+ .executeTakeFirst()
176
+
177
+ if (!model)
178
+ throw new HttpError(404, `No ${this.tableName} results found for id ${id}`)
179
+
180
+ this.mapCustomGetters(model)
181
+ await this.loadRelations(model)
182
+
183
+
184
+ return model
185
+ }
186
+
187
+ applyWhereColumn(first: keyof C, operator: Operator, second: keyof C): this {
188
+ this.selectFromQuery = this.selectFromQuery.whereRef(first, operator, second)
189
+
190
+ return this
191
+ }
192
+
193
+ whereColumn(first: keyof C, operator: Operator, second: keyof C): this {
194
+ return this.applyWhereColumn(first, operator, second)
195
+ }
196
+
197
+ applyWhereRef(column: keyof C, ...args: string[]): this {
198
+ const [operatorOrValue, value] = args
199
+ const operator = value === undefined ? '=' : operatorOrValue
200
+ const actualValue = value === undefined ? operatorOrValue : value
201
+
202
+ this.selectFromQuery = this.selectFromQuery.whereRef(column, operator, actualValue)
203
+
204
+ return this
205
+ }
206
+
207
+ whereRef(column: keyof C, ...args: string[]): this {
208
+ return this.applyWhereRef(column, ...args)
209
+ }
210
+
211
+ applyWhereRaw(sqlStatement: string): this {
212
+ this.selectFromQuery = this.selectFromQuery.where(sql`${sqlStatement}`)
213
+
214
+ return this
215
+ }
216
+
217
+ whereRaw(sqlStatement: string): this {
218
+ return this.applyWhereRaw(sqlStatement)
219
+ }
220
+
221
+ applyOrWhere(...conditions: [string, any][]): this {
222
+ this.selectFromQuery = this.selectFromQuery.where((eb: any) => {
223
+ return eb.or(
224
+ conditions.map(([column, value]) => eb(column, '=', value)),
225
+ )
226
+ })
227
+
228
+ this.updateFromQuery = this.updateFromQuery.where((eb: any) => {
229
+ return eb.or(
230
+ conditions.map(([column, value]) => eb(column, '=', value)),
231
+ )
232
+ })
233
+
234
+ this.deleteFromQuery = this.deleteFromQuery.where((eb: any) => {
235
+ return eb.or(
236
+ conditions.map(([column, value]) => eb(column, '=', value)),
237
+ )
238
+ })
239
+
240
+ return this
241
+ }
242
+
243
+ orWhere(...conditions: [string, any][]): this {
244
+ return this.applyOrWhere(...conditions)
245
+ }
246
+
247
+ applyWhen(condition: boolean, callback: (query: this) => T): this {
248
+ if (condition)
249
+ callback(this)
250
+
251
+ return this
252
+ }
253
+
254
+ when(condition: boolean, callback: (query: this) => T,
255
+ ): this {
256
+ return this.applyWhen(condition, callback)
257
+ }
258
+
259
+ applyWhereNotNull(column: keyof C): this {
260
+ this.selectFromQuery = this.selectFromQuery.where((eb: any) =>
261
+ eb(column, '=', '').or(column, 'is not', null),
262
+ )
263
+
264
+ this.updateFromQuery = this.updateFromQuery.where((eb: any) =>
265
+ eb(column, '=', '').or(column, 'is not', null),
266
+ )
267
+
268
+ this.deleteFromQuery = this.deleteFromQuery.where((eb: any) =>
269
+ eb(column, '=', '').or(column, 'is not', null),
270
+ )
271
+
272
+ return this
273
+ }
274
+
275
+ whereNotNull(column: keyof C): this {
276
+ return this.applyWhereNotNull(column)
277
+ }
278
+
279
+ applyWhereNull(column: keyof C): this {
280
+ this.selectFromQuery = this.selectFromQuery.where((eb: any) =>
281
+ eb(column, '=', '').or(column, 'is', null),
282
+ )
283
+
284
+ this.updateFromQuery = this.updateFromQuery.where((eb: any) =>
285
+ eb(column, '=', '').or(column, 'is', null),
286
+ )
287
+
288
+ this.deleteFromQuery = this.deleteFromQuery.where((eb: any) =>
289
+ eb(column, '=', '').or(column, 'is', null),
290
+ )
291
+
292
+ return this
293
+ }
294
+
295
+ whereNull(column: keyof C): this {
296
+ return this.applyWhereNull(column)
297
+ }
298
+
299
+ applyWhereIn<V>(column: keyof C, values: V[]): this {
300
+ this.selectFromQuery = this.selectFromQuery.where(column, 'in', values)
301
+
302
+ this.updateFromQuery = this.updateFromQuery.where(column, 'in', values)
303
+
304
+ this.deleteFromQuery = this.deleteFromQuery.where(column, 'in', values)
305
+
306
+ return this
307
+ }
308
+
309
+ whereIn<V = number>(column: keyof C, values: V[]): this {
310
+ return this.applyWhereIn<V>(column, values)
311
+ }
312
+
313
+ applyWhereBetween<V>(column: keyof C, range: [V, V]): this {
314
+ if (range.length !== 2) {
315
+ throw new HttpError(500, 'Range must have exactly two values: [min, max]')
316
+ }
317
+
318
+ const query = sql` ${sql.raw(column as string)} between ${range[0]} and ${range[1]} `
319
+
320
+ this.selectFromQuery = this.selectFromQuery.where(query)
321
+ this.updateFromQuery = this.updateFromQuery.where(query)
322
+ this.deleteFromQuery = this.deleteFromQuery.where(query)
323
+
324
+ return this
325
+ }
326
+
327
+ whereBetween<V = number>(column: keyof C, range: [V, V]): this {
328
+ return this.applyWhereBetween<V>(column, range)
329
+ }
330
+
331
+ applyWhereLike(column: keyof C, value: string): this {
332
+ this.selectFromQuery = this.selectFromQuery.where(sql` ${sql.raw(column as string)} LIKE ${value}`)
333
+
334
+ this.updateFromQuery = this.updateFromQuery.where(sql` ${sql.raw(column as string)} LIKE ${value}`)
335
+
336
+ this.deleteFromQuery = this.deleteFromQuery.where(sql` ${sql.raw(column as string)} LIKE ${value}`)
337
+
338
+ return this
339
+ }
340
+
341
+ whereLike(column: keyof C, value: string): this {
342
+ return this.applyWhereLike(column, value)
343
+ }
344
+
345
+ applyWhereNotIn<V>(column: keyof C, values: V[]): this {
346
+ this.selectFromQuery = this.selectFromQuery.where(column, 'not in', values)
347
+
348
+ this.updateFromQuery = this.updateFromQuery.where(column, 'not in', values)
349
+
350
+ this.deleteFromQuery = this.deleteFromQuery.where(column, 'not in', values)
351
+
352
+ return this
353
+ }
354
+
355
+ whereNotIn<V>(column: keyof C, values: V[]): this {
356
+ return this.applyWhereNotIn<V>(column, values)
357
+ }
358
+
359
+ async exists(): Promise<boolean> {
360
+ let model
361
+
362
+ if (this.hasSelect) {
363
+ model = await this.selectFromQuery.executeTakeFirst()
364
+ }
365
+ else {
366
+ model = await this.selectFromQuery.selectAll().executeTakeFirst()
367
+ }
368
+
369
+ return model !== null && model !== undefined
370
+ }
371
+
372
+ applyWith(relations: string[]): this {
373
+ this.withRelations = relations
374
+
375
+ return this
376
+ }
377
+
378
+ with(relations: string[]): this {
379
+ return this.applyWith(relations)
380
+ }
381
+
382
+ async applyLast(): Promise<T | undefined> {
383
+ let model
384
+
385
+ if (this.hasSelect) {
386
+ model = await this.selectFromQuery.executeTakeFirst()
387
+ }
388
+ else {
389
+ model = await this.selectFromQuery.selectAll().orderBy('id', 'desc').executeTakeFirst()
390
+ }
391
+
392
+ if (model) {
393
+ this.mapCustomGetters(model)
394
+ await this.loadRelations(model)
395
+ }
396
+
397
+ return model
398
+ }
399
+
400
+ async last(): Promise<T | undefined> {
401
+ const model = await this.applyLast()
402
+
403
+ if (!model)
404
+ return undefined
405
+
406
+ return model as T
407
+ }
408
+
409
+ async applyGet(): Promise<T[]> {
410
+ let models
411
+
412
+ if (this.hasSelect) {
413
+ models = await this.selectFromQuery.execute()
414
+ }
415
+ else {
416
+ models = await this.selectFromQuery.selectAll().execute()
417
+ }
418
+
419
+ this.mapCustomGetters(models)
420
+ await this.loadRelations(models)
421
+
422
+ return models as T[]
423
+ }
424
+
425
+ async get(): Promise<T[]> {
426
+ return await this.applyGet()
427
+ }
428
+
429
+ applySkip(count: number): this {
430
+ this.selectFromQuery = this.selectFromQuery.offset(count)
431
+
432
+ return this
433
+ }
434
+
435
+ skip(count: number): this {
436
+ return this.applySkip(count)
437
+ }
438
+
439
+ applyTake(count: number): this {
440
+ this.selectFromQuery = this.selectFromQuery.limit(count)
441
+
442
+ return this
443
+ }
444
+
445
+ take(count: number): this {
446
+ return this.applyTake(count)
447
+ }
448
+
449
+ async applyCount(): Promise<number> {
450
+ const result = await this.selectFromQuery
451
+ .select(sql`COUNT(*) as count`)
452
+ .executeTakeFirst()
453
+
454
+ return result.count || 0
455
+ }
456
+
457
+ async count(): Promise<number> {
458
+ return await this.applyCount()
459
+ }
460
+
461
+ applyOrderBy(column: keyof C, order: 'asc' | 'desc'): this {
462
+ this.selectFromQuery = this.selectFromQuery.orderBy(column, order)
463
+
464
+ return this
465
+ }
466
+
467
+ orderBy(column: keyof C, order: 'asc' | 'desc'): this {
468
+ return this.applyOrderBy(column, order)
469
+ }
470
+
471
+ applyGroupBy(column: keyof C): this {
472
+ this.selectFromQuery = this.selectFromQuery.groupBy(column)
473
+
474
+ return this
475
+ }
476
+
477
+ groupBy(column: keyof C): this {
478
+ return this.applyGroupBy(column)
479
+ }
480
+
481
+ applyHaving<V = string>(column: keyof C, operator: Operator, value: V): this {
482
+ this.selectFromQuery = this.selectFromQuery.having(column, operator, value)
483
+
484
+ return this
485
+ }
486
+
487
+ having<V = string>(column: keyof C, operator: Operator, value: V): this {
488
+ return this.applyHaving<V>(column, operator, value)
489
+ }
490
+
491
+ applyOrderByDesc(column: keyof C): this {
492
+ this.selectFromQuery = this.selectFromQuery.orderBy(column, 'desc')
493
+
494
+ return this
495
+ }
496
+
497
+ orderByDesc(column: keyof C): this {
498
+ return this.applyOrderByDesc(column)
499
+ }
500
+
501
+ applyOrderByAsc(column: keyof C): this {
502
+ this.selectFromQuery = this.selectFromQuery.orderBy(column, 'asc')
503
+
504
+ return this
505
+ }
506
+
507
+ orderByAsc(column: keyof C): this {
508
+ return this.applyOrderByAsc(column)
509
+ }
510
+
511
+ applyDistinct(column: keyof J): this {
512
+ this.selectFromQuery = this.selectFromQuery.select(column).distinct()
513
+
514
+ this.hasSelect = true
515
+
516
+ return this
517
+ }
518
+
519
+ distinct(column: keyof J): this {
520
+ return this.applyDistinct(column)
521
+ }
522
+
523
+ applyJoin(table: string, firstCol: string, secondCol: string): this {
524
+ this.selectFromQuery = this.selectFromQuery.innerJoin(table, firstCol, secondCol)
525
+
526
+ return this
527
+ }
528
+
529
+ join(table: string, firstCol: string, secondCol: string): this {
530
+ return this.applyJoin(table, firstCol, secondCol)
531
+ }
532
+
533
+ async applyPluck<K extends keyof T>(field: K): Promise<T[K][]> {
534
+ let models
535
+
536
+ if (this.hasSelect) {
537
+ models = await this.selectFromQuery.execute()
538
+ }
539
+ else {
540
+ models = await this.selectFromQuery.selectAll().execute()
541
+ }
542
+
543
+ return models.map((model: T) => model[field])
544
+ }
545
+
546
+ async pluck<K extends keyof T>(field: K): Promise<T[K][]> {
547
+ return await this.applyPluck(field)
548
+ }
549
+
550
+ applyInRandomOrder(): this {
551
+ this.selectFromQuery = this.selectFromQuery.orderBy(sql` ${sql.raw('RANDOM()')} `)
552
+
553
+ return this
554
+ }
555
+
556
+ inRandomOrder(): this {
557
+ return this.applyInRandomOrder()
558
+ }
559
+
560
+ applyWhereExists(callback: (qb: any) => any): this {
561
+ this.selectFromQuery = this.selectFromQuery.where(({ exists, selectFrom }: any) =>
562
+ exists(callback({ exists, selectFrom })),
563
+ )
564
+
565
+ return this
566
+ }
567
+
568
+ whereExists(callback: (qb: any) => any): this {
569
+ return this.applyWhereExists(callback)
570
+ }
571
+
572
+ applyHas(relation: string): this {
573
+ this.selectFromQuery = this.selectFromQuery.where(({ exists, selectFrom }: any) =>
574
+ exists(
575
+ selectFrom(relation)
576
+ .select('1')
577
+ .whereRef(`${relation}.${this.tableName.slice(0, -1)}_id`, '=', `${this.tableName}.id`),
578
+ ),
579
+ )
580
+
581
+ return this
582
+ }
583
+
584
+ has(relation: string): this {
585
+ return this.applyHas(relation)
586
+ }
587
+
588
+ applyDoesntHave(relation: string): this {
589
+ this.selectFromQuery = this.selectFromQuery.where(({ not, exists, selectFrom }: any) =>
590
+ not(
591
+ exists(
592
+ selectFrom(relation)
593
+ .select('1')
594
+ .whereRef(`${relation}.${this.tableName.slice(0, -1)}_id`, '=', `${this.tableName}.id`),
595
+ ),
596
+ ),
597
+ )
598
+
599
+ return this
600
+ }
601
+
602
+ doesntHave(relation: string): this {
603
+ return this.applyDoesntHave(relation)
604
+ }
605
+
606
+ applyWhereHas(relation: string, callback: (query: any) => void): this {
607
+ this.selectFromQuery = this.selectFromQuery
608
+ .where(({ exists, selectFrom }: any) => {
609
+ const subquery = selectFrom(relation)
610
+ .select('1')
611
+ .whereRef(`${relation}.${this.tableName.slice(0, -1)}_id`, '=', `${this.tableName}.id`)
612
+
613
+ callback(subquery)
614
+
615
+ return exists(subquery)
616
+ })
617
+
618
+ return this
619
+ }
620
+
621
+ whereHas(relation: string, callback: (query: any) => void): this {
622
+ return this.applyWhereHas(relation, callback)
623
+ }
624
+
625
+ applyWhereDoesntHave(relation: string, callback: (query: any) => void): this {
626
+ this.selectFromQuery = this.selectFromQuery
627
+ .where(({ exists, selectFrom, not }: any) => {
628
+ const subquery = selectFrom(relation)
629
+ .select('1')
630
+ .whereRef(`${relation}.${this.tableName.slice(0, -1)}_id`, '=', `${this.tableName}.id`)
631
+
632
+ callback(subquery)
633
+
634
+ return not(exists(subquery))
635
+ })
636
+
637
+ return this
638
+ }
639
+
640
+ whereDoesntHave(relation: string, callback: (query: any) => void): this {
641
+ return this.applyWhereDoesntHave(relation, callback)
642
+ }
643
+
644
+ async applyPaginate(options: { limit?: number, offset?: number, page?: number } = { limit: 10, offset: 0, page: 1 }): Promise<{ data: T[], paging: { total_records: number, page: number, total_pages: number }, next_cursor: number | null }> {
645
+ const totalRecordsResult = await DB.instance.selectFrom(this.tableName)
646
+ .select(DB.instance.fn.count('id').as('total'))
647
+ .executeTakeFirst()
648
+
649
+ const totalRecords = Number(totalRecordsResult?.total) || 0
650
+ const totalPages = Math.ceil(totalRecords / (options.limit ?? 10))
651
+
652
+ const modelsWithExtra = await DB.instance.selectFrom(this.tableName)
653
+ .selectAll()
654
+ .orderBy('id', 'asc')
655
+ .limit((options.limit ?? 10) + 1)
656
+ .offset(((options.page ?? 1) - 1) * (options.limit ?? 10))
657
+ .execute()
658
+
659
+ let nextCursor = null
660
+ if (modelsWithExtra.length > (options.limit ?? 10))
661
+ nextCursor = modelsWithExtra.pop()?.id ?? null
662
+
663
+ this.mapCustomGetters(modelsWithExtra)
664
+ await this.loadRelations(modelsWithExtra)
665
+
666
+ return {
667
+ data: modelsWithExtra as T[],
668
+ paging: {
669
+ total_records: totalRecords,
670
+ page: options.page || 1,
671
+ total_pages: totalPages,
672
+ },
673
+ next_cursor: nextCursor,
674
+ }
675
+ }
676
+
677
+ async paginate(options: { limit?: number, offset?: number, page?: number } = { limit: 10, offset: 0, page: 1 }): Promise<{ data: T[], paging: { total_records: number, page: number, total_pages: number }, next_cursor: number | null }> {
678
+ return await this.applyPaginate(options)
679
+ }
680
+
681
+ async applyMax(field: keyof C): Promise<number> {
682
+ const result = await this.selectFromQuery
683
+ .select(sql`MAX(${sql.raw(field as string)}) as max`)
684
+ .executeTakeFirst()
685
+
686
+ return result.max || 0
687
+ }
688
+
689
+ async max(field: keyof C): Promise<number> {
690
+ return await this.applyMax(field)
691
+ }
692
+
693
+ async applyMin(field: keyof C): Promise<number> {
694
+ const result = await this.selectFromQuery
695
+ .select(sql`MIN(${sql.raw(field as string)}) as min`)
696
+ .executeTakeFirst()
697
+
698
+ return result.min || 0
699
+ }
700
+
701
+ async min(field: keyof C): Promise<number> {
702
+ return await this.applyMin(field)
703
+ }
704
+
705
+ async applyAvg(field: keyof C): Promise<number> {
706
+ const result = await this.selectFromQuery
707
+ .select(sql`AVG(${sql.raw(field as string)}) as avg`)
708
+ .executeTakeFirst()
709
+
710
+ return result.avg || 0
711
+ }
712
+
713
+ async avg(field: keyof C): Promise<number> {
714
+ return await this.applyAvg(field)
715
+ }
716
+
717
+ async applySum(field: keyof C): Promise<number> {
718
+ const result = await this.selectFromQuery
719
+ .select(sql`SUM(${sql.raw(field as string)}) as sum`)
720
+ .executeTakeFirst()
721
+
722
+ return Number(result?.sum) || 0
723
+ }
724
+
725
+ async sum(field: keyof C): Promise<number> {
726
+ return await this.applySum(field)
727
+ }
728
+
729
+ async applyChunk(size: number, callback: (models: T[]) => Promise<void>): Promise<void> {
730
+ let page = 1
731
+ let hasMore = true
732
+
733
+ while (hasMore) {
734
+ const models = await this.selectFromQuery
735
+ .selectAll()
736
+ .limit(size)
737
+ .offset((page - 1) * size)
738
+ .execute()
739
+
740
+ if (models.length < size) {
741
+ hasMore = false
742
+ }
743
+
744
+ if (models.length > 0) {
745
+ await callback(models)
746
+ }
747
+
748
+ page++
749
+ }
750
+ }
751
+
752
+ async chunk(size: number, callback: (models: T[]) => Promise<void>): Promise<void> {
753
+ await this.applyChunk(size, callback)
754
+ }
755
+
756
+ isDirty(column?: keyof T): boolean {
757
+ if (!('attributes' in this) || !('originalAttributes' in this)) {
758
+ throw new Error('Child class must define attributes and originalAttributes properties')
759
+ }
760
+
761
+ if (column) {
762
+ return (this as any).attributes[column as string] !== (this as any).originalAttributes[column as string]
763
+ }
764
+
765
+ return Object.entries((this as any).originalAttributes).some(([key, originalValue]) => {
766
+ const currentValue = (this as any).attributes[key]
767
+
768
+ return currentValue !== originalValue
769
+ })
770
+ }
771
+
772
+ isClean(column?: keyof T): boolean {
773
+ return !this.isDirty(column)
774
+ }
775
+
776
+ wasChanged(column?: keyof T): boolean {
777
+ if (!('hasSaved' in this)) {
778
+ throw new Error('Child class must define hasSaved property')
779
+ }
780
+
781
+ return (this as any).hasSaved && this.isDirty(column)
782
+ }
783
+
784
+ getOriginal<K extends keyof T>(column?: K): K extends keyof T ? T[K] : Partial<T> {
785
+ if (!('originalAttributes' in this)) {
786
+ throw new Error('Child class must define originalAttributes property')
787
+ }
788
+
789
+ if (column) {
790
+ return (this as any).originalAttributes[column as string]
791
+ }
792
+
793
+ return (this as any).originalAttributes
794
+ }
795
+
796
+ getChanges(): Partial<T> {
797
+ if (!('attributes' in this) || !('originalAttributes' in this) || !('fillable' in this)) {
798
+ throw new Error('Child class must define attributes, originalAttributes, and fillable properties')
799
+ }
800
+
801
+ return (this as any).fillable.reduce((changes: Partial<T>, key: string) => {
802
+ const currentValue = (this as any).attributes[key]
803
+ const originalValue = (this as any).originalAttributes[key]
804
+
805
+ if (currentValue !== originalValue) {
806
+ changes[key as keyof T] = currentValue
807
+ }
808
+
809
+ return changes
810
+ }, {} as Partial<T>)
811
+ }
812
+
813
+ applyFill(data: Partial<any>): this {
814
+ if (!('attributes' in this) || !('fillable' in this) || !('guarded' in this)) {
815
+ throw new Error('Child class must define attributes, fillable, and guarded properties')
816
+ }
817
+
818
+ for (const [key, value] of Object.entries(data)) {
819
+ if (!(this as any).guarded.includes(key) && (this as any).fillable.includes(key)) {
820
+ (this as any).attributes[key] = value
821
+ }
822
+ }
823
+
824
+ return this
825
+ }
826
+
827
+ fill(data: Partial<any>): this {
828
+ return this.applyFill(data)
829
+ }
830
+
831
+ applyForceFill(data: Partial<any>): this {
832
+ if (!('attributes' in this)) {
833
+ throw new Error('Child class must define attributes property')
834
+ }
835
+
836
+ for (const [key, value] of Object.entries(data)) {
837
+ (this as any).attributes[key] = value
838
+ }
839
+
840
+ return this
841
+ }
842
+
843
+ forceFill(data: Partial<any>): this {
844
+ return this.applyForceFill(data)
845
+ }
846
+
847
+ protected mapCustomGetters(_model: T): void {}
848
+
849
+ protected async loadRelations(_model: T): Promise<void> {}
850
+ }