@mantiq/database 0.0.1
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/README.md +19 -0
- package/package.json +77 -0
- package/src/DatabaseManager.ts +115 -0
- package/src/DatabaseServiceProvider.ts +39 -0
- package/src/contracts/Connection.ts +13 -0
- package/src/contracts/Grammar.ts +16 -0
- package/src/contracts/MongoConnection.ts +122 -0
- package/src/contracts/Paginator.ts +10 -0
- package/src/drivers/BaseGrammar.ts +220 -0
- package/src/drivers/MSSQLConnection.ts +154 -0
- package/src/drivers/MSSQLGrammar.ts +106 -0
- package/src/drivers/MongoConnection.ts +298 -0
- package/src/drivers/MongoQueryBuilderImpl.ts +77 -0
- package/src/drivers/MySQLConnection.ts +120 -0
- package/src/drivers/MySQLGrammar.ts +19 -0
- package/src/drivers/PostgresConnection.ts +125 -0
- package/src/drivers/PostgresGrammar.ts +24 -0
- package/src/drivers/SQLiteConnection.ts +125 -0
- package/src/drivers/SQLiteGrammar.ts +19 -0
- package/src/errors/ConnectionError.ts +10 -0
- package/src/errors/ModelNotFoundError.ts +14 -0
- package/src/errors/QueryError.ts +11 -0
- package/src/events/DatabaseEvents.ts +101 -0
- package/src/factories/Factory.ts +170 -0
- package/src/factories/Faker.ts +382 -0
- package/src/helpers/db.ts +37 -0
- package/src/index.ts +100 -0
- package/src/migrations/Migration.ts +12 -0
- package/src/migrations/MigrationRepository.ts +50 -0
- package/src/migrations/Migrator.ts +201 -0
- package/src/orm/Collection.ts +236 -0
- package/src/orm/Document.ts +202 -0
- package/src/orm/Model.ts +775 -0
- package/src/orm/ModelQueryBuilder.ts +415 -0
- package/src/orm/Scope.ts +39 -0
- package/src/orm/eagerLoad.ts +300 -0
- package/src/query/Builder.ts +456 -0
- package/src/query/Expression.ts +18 -0
- package/src/schema/Blueprint.ts +196 -0
- package/src/schema/ColumnDefinition.ts +93 -0
- package/src/schema/SchemaBuilder.ts +376 -0
- package/src/seeders/Seeder.ts +28 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { QueryBuilder } from '../query/Builder.ts'
|
|
2
|
+
import { ModelNotFoundError } from '../errors/ModelNotFoundError.ts'
|
|
3
|
+
import { eagerLoadRelations, type EagerLoadSpec, normalizeEagerLoads } from './eagerLoad.ts'
|
|
4
|
+
import type { Model } from './Model.ts'
|
|
5
|
+
import type { DatabaseConnection } from '../contracts/Connection.ts'
|
|
6
|
+
|
|
7
|
+
export class ModelQueryBuilder<T> extends QueryBuilder {
|
|
8
|
+
private _eagerLoads: string[] = []
|
|
9
|
+
private _eagerConstraints = new Map<string, ((query: ModelQueryBuilder<any>) => void) | null>()
|
|
10
|
+
private _globalScopes = new Map<string, { apply(builder: ModelQueryBuilder<any>, model: any): void }>()
|
|
11
|
+
private _removedScopes = new Set<string>()
|
|
12
|
+
private _modelClass: any = null
|
|
13
|
+
private _scopesApplied = false
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
connection: DatabaseConnection,
|
|
17
|
+
table: string,
|
|
18
|
+
private readonly _hydrate: (row: Record<string, any>) => T,
|
|
19
|
+
private readonly softDeleteColumn: string | null = null,
|
|
20
|
+
private _withTrashed = false,
|
|
21
|
+
) {
|
|
22
|
+
super(connection, table)
|
|
23
|
+
if (softDeleteColumn && !_withTrashed) {
|
|
24
|
+
this.whereNull(softDeleteColumn)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── Eager Loading ───────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Specify relations to eager-load.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* User.query().with('posts', 'profile').get()
|
|
35
|
+
* User.query().with('posts.comments').get()
|
|
36
|
+
* User.query().with({ posts: q => q.where('published', true) }).get()
|
|
37
|
+
*/
|
|
38
|
+
with(...specs: EagerLoadSpec[]): this {
|
|
39
|
+
const normalized = normalizeEagerLoads(...specs)
|
|
40
|
+
for (const [name, constraint] of normalized) {
|
|
41
|
+
if (!this._eagerLoads.includes(name)) {
|
|
42
|
+
this._eagerLoads.push(name)
|
|
43
|
+
}
|
|
44
|
+
if (constraint) {
|
|
45
|
+
this._eagerConstraints.set(name, constraint)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Also track full dot-notation specs for nested loading
|
|
50
|
+
for (const spec of specs) {
|
|
51
|
+
if (typeof spec === 'string' && spec.includes('.')) {
|
|
52
|
+
if (!this._eagerLoads.includes(spec)) {
|
|
53
|
+
this._eagerLoads.push(spec)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return this
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
withTrashed(): this {
|
|
62
|
+
this._withTrashed = true
|
|
63
|
+
this.state.wheres = this.state.wheres.filter(
|
|
64
|
+
(w) => !(w.type === 'null' && w.column === this.softDeleteColumn),
|
|
65
|
+
)
|
|
66
|
+
return this
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onlyTrashed(): this {
|
|
70
|
+
this._withTrashed = true
|
|
71
|
+
this.state.wheres = this.state.wheres.filter(
|
|
72
|
+
(w) => !(w.type === 'null' && w.column === this.softDeleteColumn),
|
|
73
|
+
)
|
|
74
|
+
if (this.softDeleteColumn) {
|
|
75
|
+
this.whereNotNull(this.softDeleteColumn)
|
|
76
|
+
}
|
|
77
|
+
return this
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Global Scopes ─────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Register a global scope to be applied before query execution.
|
|
84
|
+
* Called by Model.query() when building the query.
|
|
85
|
+
*/
|
|
86
|
+
registerGlobalScope(name: string, scope: { apply(builder: ModelQueryBuilder<any>, model: any): void }): void {
|
|
87
|
+
this._globalScopes.set(name, scope)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Set the model class (for passing to scopes).
|
|
92
|
+
*/
|
|
93
|
+
setModel(model: any): this {
|
|
94
|
+
this._modelClass = model
|
|
95
|
+
return this
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Remove a specific global scope from this query.
|
|
100
|
+
*/
|
|
101
|
+
withoutGlobalScope(name: string): this {
|
|
102
|
+
this._removedScopes.add(name)
|
|
103
|
+
return this
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Remove all (or specific) global scopes from this query.
|
|
108
|
+
*/
|
|
109
|
+
withoutGlobalScopes(names?: string[]): this {
|
|
110
|
+
if (names) {
|
|
111
|
+
for (const name of names) this._removedScopes.add(name)
|
|
112
|
+
} else {
|
|
113
|
+
for (const name of this._globalScopes.keys()) this._removedScopes.add(name)
|
|
114
|
+
}
|
|
115
|
+
return this
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Apply registered global scopes that haven't been removed.
|
|
120
|
+
* Called lazily before query execution.
|
|
121
|
+
*/
|
|
122
|
+
private applyGlobalScopes(): void {
|
|
123
|
+
if (this._scopesApplied) return
|
|
124
|
+
this._scopesApplied = true
|
|
125
|
+
for (const [name, scope] of this._globalScopes) {
|
|
126
|
+
if (!this._removedScopes.has(name)) {
|
|
127
|
+
scope.apply(this, this._modelClass)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Hydrating read methods ─────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
override async get(): Promise<T[]> {
|
|
135
|
+
this.applyGlobalScopes()
|
|
136
|
+
const rows = await this.raw().get()
|
|
137
|
+
const models = rows.map(this._hydrate)
|
|
138
|
+
|
|
139
|
+
// Eager-load relations if any were requested
|
|
140
|
+
if (this._eagerLoads.length > 0 && models.length > 0) {
|
|
141
|
+
await eagerLoadRelations(
|
|
142
|
+
models as unknown as Model[],
|
|
143
|
+
this._eagerLoads,
|
|
144
|
+
this._eagerConstraints,
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return models
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
override async first(): Promise<T | null> {
|
|
152
|
+
this.applyGlobalScopes()
|
|
153
|
+
const row = await this.raw().first()
|
|
154
|
+
if (!row) return null
|
|
155
|
+
|
|
156
|
+
const model = this._hydrate(row)
|
|
157
|
+
|
|
158
|
+
if (this._eagerLoads.length > 0) {
|
|
159
|
+
await eagerLoadRelations(
|
|
160
|
+
[model as unknown as Model],
|
|
161
|
+
this._eagerLoads,
|
|
162
|
+
this._eagerConstraints,
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return model
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async firstOrFail(): Promise<T> {
|
|
170
|
+
const result = await this.first()
|
|
171
|
+
if (!result) throw new ModelNotFoundError(this.state.table)
|
|
172
|
+
return result
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
override async find(id: number | string): Promise<T | null> {
|
|
176
|
+
this.applyGlobalScopes()
|
|
177
|
+
const row = await this.raw().where('id', id).first()
|
|
178
|
+
if (!row) return null
|
|
179
|
+
|
|
180
|
+
const model = this._hydrate(row)
|
|
181
|
+
|
|
182
|
+
if (this._eagerLoads.length > 0) {
|
|
183
|
+
await eagerLoadRelations(
|
|
184
|
+
[model as unknown as Model],
|
|
185
|
+
this._eagerLoads,
|
|
186
|
+
this._eagerConstraints,
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return model
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async findOrFail(id: number | string): Promise<T> {
|
|
194
|
+
const result = await this.find(id)
|
|
195
|
+
if (!result) throw new ModelNotFoundError(this.state.table)
|
|
196
|
+
return result
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ── Pagination (hydrated) ─────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
override async paginate(page = 1, perPage = 15) {
|
|
202
|
+
const total = await this.count()
|
|
203
|
+
const lastPage = Math.max(1, Math.ceil(total / perPage))
|
|
204
|
+
const currentPage = Math.min(page, lastPage)
|
|
205
|
+
const originalLimit = this.state.limitValue
|
|
206
|
+
const originalOffset = this.state.offsetValue
|
|
207
|
+
this.state.limitValue = perPage
|
|
208
|
+
this.state.offsetValue = (currentPage - 1) * perPage
|
|
209
|
+
const data = await this.get()
|
|
210
|
+
this.state.limitValue = originalLimit
|
|
211
|
+
this.state.offsetValue = originalOffset
|
|
212
|
+
const from = total === 0 ? 0 : (currentPage - 1) * perPage + 1
|
|
213
|
+
const to = Math.min(from + data.length - 1, total)
|
|
214
|
+
return { data, total, perPage, currentPage, lastPage, from, to, hasMore: currentPage < lastPage }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Aggregates: delegate to raw QB to bypass hydration ────────────────────
|
|
218
|
+
|
|
219
|
+
override async count(column = '*'): Promise<number> {
|
|
220
|
+
this.applyGlobalScopes()
|
|
221
|
+
return this.raw().count(column)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
override async sum(column: string): Promise<number> {
|
|
225
|
+
this.applyGlobalScopes()
|
|
226
|
+
return this.raw().sum(column)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
override async avg(column: string): Promise<number> {
|
|
230
|
+
this.applyGlobalScopes()
|
|
231
|
+
return this.raw().avg(column)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
override async min(column: string): Promise<any> {
|
|
235
|
+
this.applyGlobalScopes()
|
|
236
|
+
return this.raw().min(column)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
override async max(column: string): Promise<any> {
|
|
240
|
+
this.applyGlobalScopes()
|
|
241
|
+
return this.raw().max(column)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
override async exists(): Promise<boolean> {
|
|
245
|
+
this.applyGlobalScopes()
|
|
246
|
+
return this.raw().exists()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
override async doesntExist(): Promise<boolean> {
|
|
250
|
+
this.applyGlobalScopes()
|
|
251
|
+
return this.raw().doesntExist()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
override async value(column: string): Promise<any> {
|
|
255
|
+
this.applyGlobalScopes()
|
|
256
|
+
return this.raw().value(column)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
override async pluck(column: string): Promise<any[]> {
|
|
260
|
+
this.applyGlobalScopes()
|
|
261
|
+
return this.raw().pluck(column)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ── Batch Processing ─────────────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Process results in chunks. The callback receives each chunk and can
|
|
268
|
+
* return false to stop processing.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* await User.query().chunk(100, async (users) => {
|
|
272
|
+
* for (const user of users) await user.process()
|
|
273
|
+
* })
|
|
274
|
+
*/
|
|
275
|
+
async chunk(size: number, callback: (items: T[], page: number) => Promise<false | void> | false | void): Promise<void> {
|
|
276
|
+
let page = 1
|
|
277
|
+
// eslint-disable-next-line no-constant-condition
|
|
278
|
+
while (true) {
|
|
279
|
+
const originalLimit = this.state.limitValue
|
|
280
|
+
const originalOffset = this.state.offsetValue
|
|
281
|
+
this.state.limitValue = size
|
|
282
|
+
this.state.offsetValue = (page - 1) * size
|
|
283
|
+
const results = await this.get()
|
|
284
|
+
this.state.limitValue = originalLimit
|
|
285
|
+
this.state.offsetValue = originalOffset
|
|
286
|
+
|
|
287
|
+
if (results.length === 0) break
|
|
288
|
+
|
|
289
|
+
const result = await callback(results, page)
|
|
290
|
+
if (result === false) break
|
|
291
|
+
if (results.length < size) break
|
|
292
|
+
|
|
293
|
+
page++
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Process results in chunks ordered by ID. More efficient for large
|
|
299
|
+
* tables because it uses WHERE id > lastId instead of OFFSET.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* await User.query().chunkById(100, async (users) => {
|
|
303
|
+
* for (const user of users) await user.process()
|
|
304
|
+
* })
|
|
305
|
+
*/
|
|
306
|
+
async chunkById(
|
|
307
|
+
size: number,
|
|
308
|
+
callback: (items: T[], page: number) => Promise<false | void> | false | void,
|
|
309
|
+
column = 'id',
|
|
310
|
+
): Promise<void> {
|
|
311
|
+
let lastId: any = null
|
|
312
|
+
let page = 1
|
|
313
|
+
// eslint-disable-next-line no-constant-condition
|
|
314
|
+
while (true) {
|
|
315
|
+
const originalLimit = this.state.limitValue
|
|
316
|
+
const originalOrders = [...this.state.orders]
|
|
317
|
+
this.state.limitValue = size
|
|
318
|
+
this.state.orders = [{ column, direction: 'asc' }]
|
|
319
|
+
|
|
320
|
+
// Clone wheres, add id constraint
|
|
321
|
+
const originalWheres = [...this.state.wheres]
|
|
322
|
+
if (lastId !== null) {
|
|
323
|
+
this.state.wheres.push({ type: 'basic', boolean: 'and', column, operator: '>', value: lastId })
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const results = await this.get()
|
|
327
|
+
|
|
328
|
+
// Restore state
|
|
329
|
+
this.state.limitValue = originalLimit
|
|
330
|
+
this.state.orders = originalOrders
|
|
331
|
+
this.state.wheres = originalWheres
|
|
332
|
+
|
|
333
|
+
if (results.length === 0) break
|
|
334
|
+
|
|
335
|
+
const lastItem = results[results.length - 1] as any
|
|
336
|
+
lastId = lastItem._attributes?.[column] ?? lastItem[column]
|
|
337
|
+
|
|
338
|
+
const result = await callback(results, page)
|
|
339
|
+
if (result === false) break
|
|
340
|
+
if (results.length < size) break
|
|
341
|
+
|
|
342
|
+
page++
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Lazily iterate over results using an async generator.
|
|
348
|
+
* Fetches rows in batches internally but yields them one at a time.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* for await (const user of User.query().cursor()) {
|
|
352
|
+
* await user.sendEmail()
|
|
353
|
+
* }
|
|
354
|
+
*/
|
|
355
|
+
async *cursor(batchSize = 200): AsyncGenerator<T, void, undefined> {
|
|
356
|
+
let offset = 0
|
|
357
|
+
// eslint-disable-next-line no-constant-condition
|
|
358
|
+
while (true) {
|
|
359
|
+
const originalLimit = this.state.limitValue
|
|
360
|
+
const originalOffset = this.state.offsetValue
|
|
361
|
+
this.state.limitValue = batchSize
|
|
362
|
+
this.state.offsetValue = offset
|
|
363
|
+
const results = await this.get()
|
|
364
|
+
this.state.limitValue = originalLimit
|
|
365
|
+
this.state.offsetValue = originalOffset
|
|
366
|
+
|
|
367
|
+
for (const item of results) {
|
|
368
|
+
yield item
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (results.length < batchSize) break
|
|
372
|
+
offset += batchSize
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ── sole() ──────────────────────────────────────────────────────────────
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Get the only record matching the query. Throws if zero or more than one.
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* const user = await User.where('email', 'admin@example.com').sole()
|
|
383
|
+
*/
|
|
384
|
+
async sole(): Promise<T> {
|
|
385
|
+
this.applyGlobalScopes()
|
|
386
|
+
const originalLimit = this.state.limitValue
|
|
387
|
+
this.state.limitValue = 2
|
|
388
|
+
const results = await this.get()
|
|
389
|
+
this.state.limitValue = originalLimit
|
|
390
|
+
|
|
391
|
+
if (results.length === 0) throw new ModelNotFoundError(this.state.table)
|
|
392
|
+
if (results.length > 1) throw new Error(`Expected one result for table [${this.state.table}], found multiple.`)
|
|
393
|
+
return results[0]!
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ── Internals ─────────────────────────────────────────────────────────────
|
|
397
|
+
|
|
398
|
+
/** Snapshot current state into a plain QueryBuilder (no hydration) */
|
|
399
|
+
private raw(): QueryBuilder {
|
|
400
|
+
const q = new QueryBuilder(this._connection, this.state.table)
|
|
401
|
+
q['state'] = {
|
|
402
|
+
table: this.state.table,
|
|
403
|
+
columns: [...this.state.columns],
|
|
404
|
+
distinct: this.state.distinct,
|
|
405
|
+
wheres: [...this.state.wheres],
|
|
406
|
+
joins: [...this.state.joins],
|
|
407
|
+
orders: [...this.state.orders],
|
|
408
|
+
groups: [...this.state.groups],
|
|
409
|
+
havings: [...this.state.havings],
|
|
410
|
+
limitValue: this.state.limitValue,
|
|
411
|
+
offsetValue: this.state.offsetValue,
|
|
412
|
+
}
|
|
413
|
+
return q
|
|
414
|
+
}
|
|
415
|
+
}
|
package/src/orm/Scope.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ModelQueryBuilder } from './ModelQueryBuilder.ts'
|
|
2
|
+
import type { Model } from './Model.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Global query scope contract.
|
|
6
|
+
*
|
|
7
|
+
* Global scopes are automatically applied to every query for a model.
|
|
8
|
+
* They can be removed at query time via `withoutGlobalScope()`.
|
|
9
|
+
*
|
|
10
|
+
* Laravel equivalent: `Illuminate\Database\Eloquent\Scope`
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* class ActiveScope implements Scope {
|
|
14
|
+
* apply(builder: ModelQueryBuilder<any>, model: typeof Model): void {
|
|
15
|
+
* builder.where('is_active', true)
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* class User extends Model {
|
|
20
|
+
* static booted() {
|
|
21
|
+
* this.addGlobalScope('active', new ActiveScope())
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
*/
|
|
25
|
+
export interface Scope {
|
|
26
|
+
apply(builder: ModelQueryBuilder<any>, model: typeof Model): void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A closure-based scope for simple cases.
|
|
31
|
+
* Wraps a callback in the Scope interface.
|
|
32
|
+
*/
|
|
33
|
+
export class ClosureScope implements Scope {
|
|
34
|
+
constructor(private readonly callback: (builder: ModelQueryBuilder<any>, model: typeof Model) => void) {}
|
|
35
|
+
|
|
36
|
+
apply(builder: ModelQueryBuilder<any>, model: typeof Model): void {
|
|
37
|
+
this.callback(builder, model)
|
|
38
|
+
}
|
|
39
|
+
}
|