@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 +850 -0
- package/dist/builder.d.ts +94 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1424 -2210
- package/dist/subquery.d.ts +37 -40
- package/dist/utils.d.ts +4 -2
- package/package.json +6 -6
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
|
+
}
|