@bunnykit/orm 0.1.3 → 0.1.5
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 +43 -3
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/model/Model.d.ts +36 -0
- package/dist/src/model/Model.js +162 -0
- package/dist/src/model/ModelNotFoundError.d.ts +5 -0
- package/dist/src/model/ModelNotFoundError.js +13 -0
- package/dist/src/query/Builder.d.ts +41 -2
- package/dist/src/query/Builder.js +308 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,11 +18,12 @@ An **Eloquent-inspired ORM** built specifically for [Bun](https://bun.sh)'s nati
|
|
|
18
18
|
- 📦 **Multi-database** — SQLite, MySQL, and PostgreSQL support
|
|
19
19
|
- 🔷 **Fully Typed** — Written in TypeScript with generics everywhere
|
|
20
20
|
- 🏗️ **Schema Builder** — Programmatic table creation, indexes, foreign keys
|
|
21
|
-
- 🔍 **Query Builder** — Chainable `where`, `join`, `orderBy`, `groupBy`, etc.
|
|
22
|
-
- 🧬 **Eloquent-style Models** — Property attributes, defaults, casts, dirty tracking, soft deletes, scopes
|
|
21
|
+
- 🔍 **Query Builder** — Chainable `where`, `join`, `orderBy`, `groupBy`, date filters, conditional building, etc.
|
|
22
|
+
- 🧬 **Eloquent-style Models** — Property attributes, defaults, casts, dirty tracking, soft deletes, scopes, find-or-fail, first-or-create
|
|
23
23
|
- 🔗 **Relations** — Standard, many-to-many, polymorphic, through, one-of-many, and relation queries
|
|
24
24
|
- 👁️ **Observers** — Lifecycle hooks (`creating`, `created`, `updating`, `updated`, etc.)
|
|
25
25
|
- 🚀 **Migrations & CLI** — Create, run, and rollback migrations from the command line
|
|
26
|
+
- ⚡ **Streaming** — `chunk`, `cursor`, `each`, and `lazy` for memory-efficient large dataset processing
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
@@ -261,6 +262,14 @@ User.where({ role: "admin", active: true });
|
|
|
261
262
|
User.whereIn("id", [1, 2, 3]);
|
|
262
263
|
User.whereNull("deleted_at");
|
|
263
264
|
User.whereNotNull("email");
|
|
265
|
+
User.whereNot("status", "banned");
|
|
266
|
+
|
|
267
|
+
// Date filtering (cross-database)
|
|
268
|
+
Event.whereDate("happened_at", "2024-01-01");
|
|
269
|
+
Event.whereYear("created_at", ">=", 2023);
|
|
270
|
+
Event.whereMonth("birthday", 12);
|
|
271
|
+
Event.whereDay("anniversary", 14);
|
|
272
|
+
Event.whereTime("opened_at", "09:00:00");
|
|
264
273
|
|
|
265
274
|
// Chaining
|
|
266
275
|
const results = await User
|
|
@@ -271,6 +280,16 @@ const results = await User
|
|
|
271
280
|
.offset(0)
|
|
272
281
|
.get();
|
|
273
282
|
|
|
283
|
+
// Conditional building
|
|
284
|
+
User.when(filters.name, (q) => q.where("name", filters.name))
|
|
285
|
+
.when(filters.age, (q) => q.where("age", ">=", filters.age))
|
|
286
|
+
.unless(showAll, (q) => q.where("active", true))
|
|
287
|
+
.tap((q) => console.log(q.toSql()));
|
|
288
|
+
|
|
289
|
+
// Ordering convenience
|
|
290
|
+
Post.latest().first(); // orderBy created_at desc
|
|
291
|
+
Post.oldest("published_at"); // orderBy published_at asc
|
|
292
|
+
|
|
274
293
|
// Aggregates
|
|
275
294
|
const count = await User.where("active", true).count();
|
|
276
295
|
const exists = await User.where("email", "test@example.com").exists();
|
|
@@ -288,6 +307,16 @@ const emails = await User.pluck("email");
|
|
|
288
307
|
// First / Find
|
|
289
308
|
const user = await User.where("email", "alice@example.com").first();
|
|
290
309
|
const byId = await User.find(1);
|
|
310
|
+
|
|
311
|
+
// Find-or-Fail (throws if not found)
|
|
312
|
+
const user = await User.findOrFail(1);
|
|
313
|
+
const first = await User.firstOrFail();
|
|
314
|
+
|
|
315
|
+
// Streaming large datasets
|
|
316
|
+
await User.chunk(100, (users) => { ... });
|
|
317
|
+
await User.each(100, (user) => { ... });
|
|
318
|
+
for await (const user of User.cursor()) { ... }
|
|
319
|
+
for await (const user of User.lazy(500)) { ... }
|
|
291
320
|
```
|
|
292
321
|
|
|
293
322
|
---
|
|
@@ -345,7 +374,18 @@ user.getDirty(); // { name: "Charlie" }
|
|
|
345
374
|
await user.save();
|
|
346
375
|
await user.delete();
|
|
347
376
|
await user.refresh();
|
|
377
|
+
await user.touch(); // update only timestamps
|
|
378
|
+
await user.load("posts"); // lazy eager loading
|
|
348
379
|
user.toJSON(); // plain object
|
|
380
|
+
|
|
381
|
+
// Increment / Decrement
|
|
382
|
+
await user.increment("login_count");
|
|
383
|
+
await user.increment("login_count", 5, { last_login_at: new Date() });
|
|
384
|
+
await user.decrement("stock", 10);
|
|
385
|
+
|
|
386
|
+
// First-or-Create / Update-or-Create
|
|
387
|
+
const user = await User.firstOrCreate({ email: "alice@example.com" }, { name: "Alice" });
|
|
388
|
+
const user = await User.updateOrCreate({ email: "alice@example.com" }, { name: "Alice Smith" });
|
|
349
389
|
```
|
|
350
390
|
|
|
351
391
|
### Default Attributes
|
|
@@ -932,7 +972,7 @@ Bunny includes a full test suite built with `bun:test`.
|
|
|
932
972
|
bun test
|
|
933
973
|
```
|
|
934
974
|
|
|
935
|
-
|
|
975
|
+
139 tests covering connection management, schema grammars, query builder, model CRUD, casts, scopes, soft deletes, relations, observers, migrations, type generation, lazy eager loading, find-or-fail, first-or-create, increment/decrement, touch, chunk/cursor/lazy streaming, date where clauses, conditional query building, whereNot, and latest/oldest.
|
|
936
976
|
|
|
937
977
|
---
|
|
938
978
|
|
package/dist/src/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { PostgresGrammar } from "./schema/grammars/PostgresGrammar.js";
|
|
|
9
9
|
export { Builder } from "./query/Builder.js";
|
|
10
10
|
export { Model, HasMany, BelongsTo, HasOne, HasManyThrough, HasOneThrough } from "./model/Model.js";
|
|
11
11
|
export type { ModelConstructor, GlobalScope, CastDefinition, CastsAttributes } from "./model/Model.js";
|
|
12
|
+
export { ModelNotFoundError } from "./model/ModelNotFoundError.js";
|
|
12
13
|
export { ObserverRegistry, type ObserverContract } from "./model/Observer.js";
|
|
13
14
|
export { MorphMap } from "./model/MorphMap.js";
|
|
14
15
|
export { MorphTo, MorphOne, MorphMany, MorphToMany } from "./model/MorphRelations.js";
|
package/dist/src/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { MySqlGrammar } from "./schema/grammars/MySqlGrammar.js";
|
|
|
7
7
|
export { PostgresGrammar } from "./schema/grammars/PostgresGrammar.js";
|
|
8
8
|
export { Builder } from "./query/Builder.js";
|
|
9
9
|
export { Model, HasMany, BelongsTo, HasOne, HasManyThrough, HasOneThrough } from "./model/Model.js";
|
|
10
|
+
export { ModelNotFoundError } from "./model/ModelNotFoundError.js";
|
|
10
11
|
export { ObserverRegistry } from "./model/Observer.js";
|
|
11
12
|
export { MorphMap } from "./model/MorphMap.js";
|
|
12
13
|
export { MorphTo, MorphOne, MorphMany, MorphToMany } from "./model/MorphRelations.js";
|
|
@@ -109,12 +109,39 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
109
109
|
static shouldAutoGeneratePrimaryKey(): Promise<boolean>;
|
|
110
110
|
static create<M extends typeof Model>(this: M, attributes: Partial<InstanceType<M> extends Model<infer U> ? U : Record<string, any>>): Promise<InstanceType<M>>;
|
|
111
111
|
static find<M extends typeof Model>(this: M, id: any): Promise<InstanceType<M> | null>;
|
|
112
|
+
static findOrFail<M extends typeof Model>(this: M, id: any): Promise<InstanceType<M>>;
|
|
112
113
|
static first<M extends typeof Model>(this: M): Promise<InstanceType<M> | null>;
|
|
114
|
+
static firstOrFail<M extends typeof Model>(this: M): Promise<InstanceType<M>>;
|
|
115
|
+
static firstOrCreate<M extends typeof Model>(this: M, attributes?: Record<string, any>, values?: Record<string, any>): Promise<InstanceType<M>>;
|
|
116
|
+
static updateOrCreate<M extends typeof Model>(this: M, attributes: Record<string, any>, values?: Record<string, any>): Promise<InstanceType<M>>;
|
|
113
117
|
static where<M extends typeof Model>(this: M, column: string | Record<string, any>, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
118
|
+
static orderBy<M extends typeof Model>(this: M, column: string, direction?: "asc" | "desc"): Builder<InstanceType<M>>;
|
|
114
119
|
static whereIn<M extends typeof Model>(this: M, column: string, values: any[]): Builder<InstanceType<M>>;
|
|
115
120
|
static whereNull<M extends typeof Model>(this: M, column: string): Builder<InstanceType<M>>;
|
|
116
121
|
static whereNotNull<M extends typeof Model>(this: M, column: string): Builder<InstanceType<M>>;
|
|
117
122
|
static orWhere<M extends typeof Model>(this: M, column: string | Record<string, any>, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
123
|
+
static whereNot<M extends typeof Model>(this: M, column: string | Record<string, any>, value?: any): Builder<InstanceType<M>>;
|
|
124
|
+
static orWhereNot<M extends typeof Model>(this: M, column: string | Record<string, any>, value?: any): Builder<InstanceType<M>>;
|
|
125
|
+
static whereDate<M extends typeof Model>(this: M, column: string, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
126
|
+
static orWhereDate<M extends typeof Model>(this: M, column: string, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
127
|
+
static whereDay<M extends typeof Model>(this: M, column: string, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
128
|
+
static orWhereDay<M extends typeof Model>(this: M, column: string, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
129
|
+
static whereMonth<M extends typeof Model>(this: M, column: string, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
130
|
+
static orWhereMonth<M extends typeof Model>(this: M, column: string, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
131
|
+
static whereYear<M extends typeof Model>(this: M, column: string, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
132
|
+
static orWhereYear<M extends typeof Model>(this: M, column: string, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
133
|
+
static whereTime<M extends typeof Model>(this: M, column: string, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
134
|
+
static orWhereTime<M extends typeof Model>(this: M, column: string, operator?: string | any, value?: any): Builder<InstanceType<M>>;
|
|
135
|
+
static latest<M extends typeof Model>(this: M, column?: string): Builder<InstanceType<M>>;
|
|
136
|
+
static oldest<M extends typeof Model>(this: M, column?: string): Builder<InstanceType<M>>;
|
|
137
|
+
static when<M extends typeof Model>(this: M, condition: any, callback: (query: Builder<any>) => void | Builder<any>, defaultCallback?: (query: Builder<any>) => void | Builder<any>): Builder<InstanceType<M>>;
|
|
138
|
+
static unless<M extends typeof Model>(this: M, condition: any, callback: (query: Builder<any>) => void | Builder<any>, defaultCallback?: (query: Builder<any>) => void | Builder<any>): Builder<InstanceType<M>>;
|
|
139
|
+
static tap<M extends typeof Model>(this: M, callback: (query: Builder<any>) => void | Builder<any>): Builder<InstanceType<M>>;
|
|
140
|
+
static take<M extends typeof Model>(this: M, count: number): Builder<InstanceType<M>>;
|
|
141
|
+
static skip<M extends typeof Model>(this: M, count: number): Builder<InstanceType<M>>;
|
|
142
|
+
static inRandomOrder<M extends typeof Model>(this: M): Builder<InstanceType<M>>;
|
|
143
|
+
static lockForUpdate<M extends typeof Model>(this: M): Builder<InstanceType<M>>;
|
|
144
|
+
static sharedLock<M extends typeof Model>(this: M): Builder<InstanceType<M>>;
|
|
118
145
|
static with<M extends typeof Model>(this: M, ...relations: string[]): Builder<InstanceType<M>>;
|
|
119
146
|
static withTrashed<M extends typeof Model>(this: M): Builder<InstanceType<M>>;
|
|
120
147
|
static onlyTrashed<M extends typeof Model>(this: M): Builder<InstanceType<M>>;
|
|
@@ -131,6 +158,10 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
131
158
|
static withMax<M extends typeof Model>(this: M, relationName: string, column: string, alias?: string): Builder<InstanceType<M>>;
|
|
132
159
|
static all<M extends typeof Model>(this: M): Promise<InstanceType<M>[]>;
|
|
133
160
|
static paginate<M extends typeof Model>(this: M, perPage?: number, page?: number): Promise<import("../query/Builder.js").Paginator<InstanceType<M>>>;
|
|
161
|
+
static chunk<M extends typeof Model>(this: M, count: number, callback: (items: InstanceType<M>[]) => void | Promise<void>): Promise<void>;
|
|
162
|
+
static each<M extends typeof Model>(this: M, count: number, callback: (item: InstanceType<M>) => void | Promise<void>): Promise<void>;
|
|
163
|
+
static cursor<M extends typeof Model>(this: M): AsyncGenerator<InstanceType<M>>;
|
|
164
|
+
static lazy<M extends typeof Model>(this: M, count?: number): AsyncGenerator<InstanceType<M>>;
|
|
134
165
|
static eagerLoadRelations(models: Model[], relations: string[]): Promise<void>;
|
|
135
166
|
static eagerLoadRelation(models: Model[], relationName: string): Promise<void>;
|
|
136
167
|
fill(attributes: Partial<T>): this;
|
|
@@ -147,6 +178,11 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
147
178
|
getDirty(): Partial<T>;
|
|
148
179
|
isDirty(): boolean;
|
|
149
180
|
save(): Promise<this>;
|
|
181
|
+
updateTimestamps(): void;
|
|
182
|
+
touch(): Promise<boolean>;
|
|
183
|
+
increment(column: string, amount?: number, extra?: Record<string, any>): Promise<this>;
|
|
184
|
+
decrement(column: string, amount?: number, extra?: Record<string, any>): Promise<this>;
|
|
185
|
+
load(...relations: string[]): Promise<this>;
|
|
150
186
|
delete(): Promise<boolean>;
|
|
151
187
|
restore(): Promise<boolean>;
|
|
152
188
|
forceDelete(): Promise<boolean>;
|
package/dist/src/model/Model.js
CHANGED
|
@@ -4,6 +4,7 @@ import { ObserverRegistry } from "./Observer.js";
|
|
|
4
4
|
import { MorphTo, MorphOne, MorphMany, MorphToMany } from "./MorphRelations.js";
|
|
5
5
|
import { BelongsToMany } from "./BelongsToMany.js";
|
|
6
6
|
import { Schema } from "../schema/Schema.js";
|
|
7
|
+
import { ModelNotFoundError } from "./ModelNotFoundError.js";
|
|
7
8
|
const globalScopes = new WeakMap();
|
|
8
9
|
function getGlobalScopes(model) {
|
|
9
10
|
const scopes = new Map();
|
|
@@ -397,12 +398,44 @@ export class Model {
|
|
|
397
398
|
static async find(id) {
|
|
398
399
|
return this.query().find(id, this.primaryKey);
|
|
399
400
|
}
|
|
401
|
+
static async findOrFail(id) {
|
|
402
|
+
const result = await this.find(id);
|
|
403
|
+
if (!result) {
|
|
404
|
+
throw new ModelNotFoundError(this.name, id);
|
|
405
|
+
}
|
|
406
|
+
return result;
|
|
407
|
+
}
|
|
400
408
|
static async first() {
|
|
401
409
|
return this.query().first();
|
|
402
410
|
}
|
|
411
|
+
static async firstOrFail() {
|
|
412
|
+
const result = await this.first();
|
|
413
|
+
if (!result) {
|
|
414
|
+
throw new ModelNotFoundError(this.name);
|
|
415
|
+
}
|
|
416
|
+
return result;
|
|
417
|
+
}
|
|
418
|
+
static async firstOrCreate(attributes = {}, values = {}) {
|
|
419
|
+
const found = await this.where(attributes).first();
|
|
420
|
+
if (found)
|
|
421
|
+
return found;
|
|
422
|
+
return this.create({ ...attributes, ...values });
|
|
423
|
+
}
|
|
424
|
+
static async updateOrCreate(attributes, values = {}) {
|
|
425
|
+
const found = await this.where(attributes).first();
|
|
426
|
+
if (found) {
|
|
427
|
+
found.fill(values);
|
|
428
|
+
await found.save();
|
|
429
|
+
return found;
|
|
430
|
+
}
|
|
431
|
+
return this.create({ ...attributes, ...values });
|
|
432
|
+
}
|
|
403
433
|
static where(column, operator, value) {
|
|
404
434
|
return this.query().where(column, operator, value);
|
|
405
435
|
}
|
|
436
|
+
static orderBy(column, direction) {
|
|
437
|
+
return this.query().orderBy(column, direction);
|
|
438
|
+
}
|
|
406
439
|
static whereIn(column, values) {
|
|
407
440
|
return this.query().whereIn(column, values);
|
|
408
441
|
}
|
|
@@ -415,6 +448,72 @@ export class Model {
|
|
|
415
448
|
static orWhere(column, operator, value) {
|
|
416
449
|
return this.query().orWhere(column, operator, value);
|
|
417
450
|
}
|
|
451
|
+
static whereNot(column, value) {
|
|
452
|
+
return this.query().whereNot(column, value);
|
|
453
|
+
}
|
|
454
|
+
static orWhereNot(column, value) {
|
|
455
|
+
return this.query().orWhereNot(column, value);
|
|
456
|
+
}
|
|
457
|
+
static whereDate(column, operator, value) {
|
|
458
|
+
return this.query().whereDate(column, operator, value);
|
|
459
|
+
}
|
|
460
|
+
static orWhereDate(column, operator, value) {
|
|
461
|
+
return this.query().orWhereDate(column, operator, value);
|
|
462
|
+
}
|
|
463
|
+
static whereDay(column, operator, value) {
|
|
464
|
+
return this.query().whereDay(column, operator, value);
|
|
465
|
+
}
|
|
466
|
+
static orWhereDay(column, operator, value) {
|
|
467
|
+
return this.query().orWhereDay(column, operator, value);
|
|
468
|
+
}
|
|
469
|
+
static whereMonth(column, operator, value) {
|
|
470
|
+
return this.query().whereMonth(column, operator, value);
|
|
471
|
+
}
|
|
472
|
+
static orWhereMonth(column, operator, value) {
|
|
473
|
+
return this.query().orWhereMonth(column, operator, value);
|
|
474
|
+
}
|
|
475
|
+
static whereYear(column, operator, value) {
|
|
476
|
+
return this.query().whereYear(column, operator, value);
|
|
477
|
+
}
|
|
478
|
+
static orWhereYear(column, operator, value) {
|
|
479
|
+
return this.query().orWhereYear(column, operator, value);
|
|
480
|
+
}
|
|
481
|
+
static whereTime(column, operator, value) {
|
|
482
|
+
return this.query().whereTime(column, operator, value);
|
|
483
|
+
}
|
|
484
|
+
static orWhereTime(column, operator, value) {
|
|
485
|
+
return this.query().orWhereTime(column, operator, value);
|
|
486
|
+
}
|
|
487
|
+
static latest(column) {
|
|
488
|
+
return this.query().latest(column);
|
|
489
|
+
}
|
|
490
|
+
static oldest(column) {
|
|
491
|
+
return this.query().oldest(column);
|
|
492
|
+
}
|
|
493
|
+
static when(condition, callback, defaultCallback) {
|
|
494
|
+
return this.query().when(condition, callback, defaultCallback);
|
|
495
|
+
}
|
|
496
|
+
static unless(condition, callback, defaultCallback) {
|
|
497
|
+
return this.query().unless(condition, callback, defaultCallback);
|
|
498
|
+
}
|
|
499
|
+
static tap(callback) {
|
|
500
|
+
return this.query().tap(callback);
|
|
501
|
+
}
|
|
502
|
+
static take(count) {
|
|
503
|
+
return this.query().take(count);
|
|
504
|
+
}
|
|
505
|
+
static skip(count) {
|
|
506
|
+
return this.query().skip(count);
|
|
507
|
+
}
|
|
508
|
+
static inRandomOrder() {
|
|
509
|
+
return this.query().inRandomOrder();
|
|
510
|
+
}
|
|
511
|
+
static lockForUpdate() {
|
|
512
|
+
return this.query().lockForUpdate();
|
|
513
|
+
}
|
|
514
|
+
static sharedLock() {
|
|
515
|
+
return this.query().sharedLock();
|
|
516
|
+
}
|
|
418
517
|
static with(...relations) {
|
|
419
518
|
return this.query().with(...relations);
|
|
420
519
|
}
|
|
@@ -463,6 +562,18 @@ export class Model {
|
|
|
463
562
|
static async paginate(perPage, page) {
|
|
464
563
|
return this.query().paginate(perPage, page);
|
|
465
564
|
}
|
|
565
|
+
static async chunk(count, callback) {
|
|
566
|
+
return this.query().chunk(count, callback);
|
|
567
|
+
}
|
|
568
|
+
static async each(count, callback) {
|
|
569
|
+
return this.query().each(count, callback);
|
|
570
|
+
}
|
|
571
|
+
static cursor() {
|
|
572
|
+
return this.query().cursor();
|
|
573
|
+
}
|
|
574
|
+
static lazy(count) {
|
|
575
|
+
return this.query().lazy(count);
|
|
576
|
+
}
|
|
466
577
|
static async eagerLoadRelations(models, relations) {
|
|
467
578
|
for (const relationName of relations) {
|
|
468
579
|
if (relationName.includes(".")) {
|
|
@@ -673,6 +784,57 @@ export class Model {
|
|
|
673
784
|
}
|
|
674
785
|
return this;
|
|
675
786
|
}
|
|
787
|
+
updateTimestamps() {
|
|
788
|
+
const constructor = this.constructor;
|
|
789
|
+
if (!constructor.timestamps)
|
|
790
|
+
return;
|
|
791
|
+
const now = this.freshTimestamp();
|
|
792
|
+
this.$attributes["updated_at"] = now;
|
|
793
|
+
if (!this.$exists) {
|
|
794
|
+
this.$attributes["created_at"] = now;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
async touch() {
|
|
798
|
+
if (!this.$exists)
|
|
799
|
+
return false;
|
|
800
|
+
const constructor = this.constructor;
|
|
801
|
+
if (!constructor.timestamps)
|
|
802
|
+
return false;
|
|
803
|
+
const now = this.freshTimestamp();
|
|
804
|
+
const pk = this.getAttribute(constructor.primaryKey);
|
|
805
|
+
await new Builder(constructor.getConnection(), constructor.getTable())
|
|
806
|
+
.where(constructor.primaryKey, pk)
|
|
807
|
+
.update({ updated_at: now });
|
|
808
|
+
this.$attributes["updated_at"] = now;
|
|
809
|
+
this.$original = { ...this.$attributes };
|
|
810
|
+
return true;
|
|
811
|
+
}
|
|
812
|
+
async increment(column, amount = 1, extra = {}) {
|
|
813
|
+
const constructor = this.constructor;
|
|
814
|
+
const pk = this.getAttribute(constructor.primaryKey);
|
|
815
|
+
if (!pk)
|
|
816
|
+
return this;
|
|
817
|
+
const builder = new Builder(constructor.getConnection(), constructor.getTable())
|
|
818
|
+
.where(constructor.primaryKey, pk);
|
|
819
|
+
if (constructor.timestamps) {
|
|
820
|
+
extra = { ...extra, updated_at: this.freshTimestamp() };
|
|
821
|
+
}
|
|
822
|
+
await builder.increment(column, amount, extra);
|
|
823
|
+
this.$attributes[column] = (this.$attributes[column] || 0) + amount;
|
|
824
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
825
|
+
this.$attributes[key] = value;
|
|
826
|
+
}
|
|
827
|
+
this.$original = { ...this.$attributes };
|
|
828
|
+
return this;
|
|
829
|
+
}
|
|
830
|
+
async decrement(column, amount = 1, extra = {}) {
|
|
831
|
+
return this.increment(column, -amount, extra);
|
|
832
|
+
}
|
|
833
|
+
async load(...relations) {
|
|
834
|
+
const constructor = this.constructor;
|
|
835
|
+
await constructor.eagerLoadRelations([this], relations);
|
|
836
|
+
return this;
|
|
837
|
+
}
|
|
676
838
|
async delete() {
|
|
677
839
|
const constructor = this.constructor;
|
|
678
840
|
await ObserverRegistry.dispatch("deleting", this);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class ModelNotFoundError extends Error {
|
|
2
|
+
modelName;
|
|
3
|
+
identifiers;
|
|
4
|
+
constructor(modelName, identifiers) {
|
|
5
|
+
const msg = identifiers !== undefined
|
|
6
|
+
? `No query results for model [${modelName}] ${JSON.stringify(identifiers)}`
|
|
7
|
+
: `No query results for model [${modelName}]`;
|
|
8
|
+
super(msg);
|
|
9
|
+
this.name = "ModelNotFoundError";
|
|
10
|
+
this.modelName = modelName;
|
|
11
|
+
this.identifiers = identifiers;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -25,23 +25,41 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
25
25
|
distinctFlag: boolean;
|
|
26
26
|
model?: typeof Model;
|
|
27
27
|
eagerLoads: string[];
|
|
28
|
+
randomOrderFlag: boolean;
|
|
29
|
+
lockMode?: string;
|
|
28
30
|
constructor(connection: Connection, table: string);
|
|
29
31
|
setModel(model: typeof Model): this;
|
|
30
32
|
table(table: string): this;
|
|
31
33
|
select(...columns: string[]): this;
|
|
32
34
|
distinct(): this;
|
|
33
|
-
where(column: string | Record<string, any
|
|
34
|
-
|
|
35
|
+
where(column: string | Record<string, any> | ((query: Builder<T>) => void), operator?: string | any, value?: any, boolean?: "and" | "or", scope?: string): this;
|
|
36
|
+
private whereNested;
|
|
37
|
+
orWhere(column: string | Record<string, any> | ((query: Builder<T>) => void), operator?: string | any, value?: any): this;
|
|
38
|
+
whereNot(column: string | Record<string, any>, value?: any, boolean?: "and" | "or"): this;
|
|
39
|
+
orWhereNot(column: string | Record<string, any>, value?: any): this;
|
|
35
40
|
whereIn(column: string, values: any[], boolean?: "and" | "or", scope?: string): this;
|
|
36
41
|
whereNotIn(column: string, values: any[], boolean?: "and" | "or", scope?: string): this;
|
|
37
42
|
whereNull(column: string, boolean?: "and" | "or", scope?: string): this;
|
|
38
43
|
whereNotNull(column: string, boolean?: "and" | "or", scope?: string): this;
|
|
39
44
|
whereBetween(column: string, values: [any, any], boolean?: "and" | "or", scope?: string): this;
|
|
40
45
|
whereNotBetween(column: string, values: [any, any], boolean?: "and" | "or", scope?: string): this;
|
|
46
|
+
whereDate(column: string, operator?: string | any, value?: any, boolean?: "and" | "or"): this;
|
|
47
|
+
orWhereDate(column: string, operator?: string | any, value?: any): this;
|
|
48
|
+
whereDay(column: string, operator?: string | any, value?: any, boolean?: "and" | "or"): this;
|
|
49
|
+
orWhereDay(column: string, operator?: string | any, value?: any): this;
|
|
50
|
+
whereMonth(column: string, operator?: string | any, value?: any, boolean?: "and" | "or"): this;
|
|
51
|
+
orWhereMonth(column: string, operator?: string | any, value?: any): this;
|
|
52
|
+
whereYear(column: string, operator?: string | any, value?: any, boolean?: "and" | "or"): this;
|
|
53
|
+
orWhereYear(column: string, operator?: string | any, value?: any): this;
|
|
54
|
+
whereTime(column: string, operator?: string | any, value?: any, boolean?: "and" | "or"): this;
|
|
55
|
+
orWhereTime(column: string, operator?: string | any, value?: any): this;
|
|
41
56
|
whereRaw(sql: string, boolean?: "and" | "or", scope?: string): this;
|
|
42
57
|
whereColumn(first: string, operator: string, second: string, boolean?: "and" | "or"): this;
|
|
43
58
|
whereExists(sql: string, boolean?: "and" | "or", not?: boolean): this;
|
|
44
59
|
orderBy(column: string, direction?: "asc" | "desc"): this;
|
|
60
|
+
latest(column?: string): this;
|
|
61
|
+
oldest(column?: string): this;
|
|
62
|
+
inRandomOrder(): this;
|
|
45
63
|
groupBy(...columns: string[]): this;
|
|
46
64
|
having(column: string, operator: string, value: any): this;
|
|
47
65
|
limit(count: number): this;
|
|
@@ -56,6 +74,9 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
56
74
|
withTrashed(): this;
|
|
57
75
|
onlyTrashed(): this;
|
|
58
76
|
scope(name: string, ...args: any[]): this;
|
|
77
|
+
when(condition: any, callback: (query: this) => void | this, defaultCallback?: (query: this) => void | this): this;
|
|
78
|
+
unless(condition: any, callback: (query: this) => void | this, defaultCallback?: (query: this) => void | this): this;
|
|
79
|
+
tap(callback: (query: this) => void | this): this;
|
|
59
80
|
has(relationName: string, operator?: string | RelationConstraint, count?: number, callback?: RelationConstraint): this;
|
|
60
81
|
orHas(relationName: string, operator?: string | RelationConstraint, count?: number, callback?: RelationConstraint): this;
|
|
61
82
|
whereHas(relationName: string, callback?: RelationConstraint, operator?: string, count?: number): this;
|
|
@@ -74,7 +95,9 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
74
95
|
private wrap;
|
|
75
96
|
private wrapValue;
|
|
76
97
|
private escape;
|
|
98
|
+
private compileWhereClause;
|
|
77
99
|
private compileWheres;
|
|
100
|
+
private compileNestedWheres;
|
|
78
101
|
private compileOrders;
|
|
79
102
|
private compileGroups;
|
|
80
103
|
private compileHavings;
|
|
@@ -86,6 +109,10 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
86
109
|
get(): Promise<T[]>;
|
|
87
110
|
first(): Promise<T | null>;
|
|
88
111
|
find(id: any, column?: string): Promise<T | null>;
|
|
112
|
+
findOrFail(id: any, column?: string): Promise<T>;
|
|
113
|
+
firstOrFail(): Promise<T>;
|
|
114
|
+
firstOrCreate(attributes?: Partial<T>, values?: Partial<T>): Promise<T>;
|
|
115
|
+
updateOrCreate(attributes: Partial<T>, values?: Partial<T>): Promise<T>;
|
|
89
116
|
pluck(column: string): Promise<any[]>;
|
|
90
117
|
private aggregate;
|
|
91
118
|
count(column?: string): Promise<number>;
|
|
@@ -94,12 +121,24 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
94
121
|
min(column: string): Promise<any>;
|
|
95
122
|
max(column: string): Promise<any>;
|
|
96
123
|
paginate(perPage?: number, page?: number): Promise<Paginator<T>>;
|
|
124
|
+
chunk(count: number, callback: (items: T[]) => void | Promise<void>): Promise<void>;
|
|
125
|
+
each(count: number, callback: (item: T) => void | Promise<void>): Promise<void>;
|
|
126
|
+
cursor(): AsyncGenerator<T>;
|
|
127
|
+
lazy(count?: number): AsyncGenerator<T>;
|
|
97
128
|
insert(data: Partial<T> | Partial<T>[]): Promise<any>;
|
|
98
129
|
insertGetId(data: Partial<T>, idColumn?: string): Promise<any>;
|
|
99
130
|
update(data: Partial<T>): Promise<any>;
|
|
100
131
|
delete(): Promise<any>;
|
|
132
|
+
increment(column: string, amount?: number, extra?: Record<string, any>): Promise<any>;
|
|
133
|
+
decrement(column: string, amount?: number, extra?: Record<string, any>): Promise<any>;
|
|
101
134
|
restore(): Promise<any>;
|
|
102
135
|
exists(): Promise<boolean>;
|
|
136
|
+
doesntExist(): Promise<boolean>;
|
|
137
|
+
take(count: number): this;
|
|
138
|
+
skip(count: number): this;
|
|
139
|
+
lockForUpdate(): this;
|
|
140
|
+
sharedLock(): this;
|
|
141
|
+
private addDateWhere;
|
|
103
142
|
private getModelRelation;
|
|
104
143
|
private withAggregate;
|
|
105
144
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ModelNotFoundError } from "../model/ModelNotFoundError.js";
|
|
1
2
|
export class Builder {
|
|
2
3
|
connection;
|
|
3
4
|
tableName;
|
|
@@ -12,6 +13,8 @@ export class Builder {
|
|
|
12
13
|
distinctFlag = false;
|
|
13
14
|
model;
|
|
14
15
|
eagerLoads = [];
|
|
16
|
+
randomOrderFlag = false;
|
|
17
|
+
lockMode;
|
|
15
18
|
constructor(connection, table) {
|
|
16
19
|
this.connection = connection;
|
|
17
20
|
this.tableName = table;
|
|
@@ -33,6 +36,9 @@ export class Builder {
|
|
|
33
36
|
return this;
|
|
34
37
|
}
|
|
35
38
|
where(column, operator, value, boolean = "and", scope) {
|
|
39
|
+
if (typeof column === "function") {
|
|
40
|
+
return this.whereNested(column, boolean);
|
|
41
|
+
}
|
|
36
42
|
if (typeof column === "object" && column !== null) {
|
|
37
43
|
for (const [key, val] of Object.entries(column)) {
|
|
38
44
|
this.where(key, "=", val, boolean, scope);
|
|
@@ -46,9 +52,30 @@ export class Builder {
|
|
|
46
52
|
this.wheres.push({ type: "basic", column, operator, value, boolean, scope });
|
|
47
53
|
return this;
|
|
48
54
|
}
|
|
55
|
+
whereNested(callback, boolean = "and") {
|
|
56
|
+
const nested = new Builder(this.connection, this.tableName);
|
|
57
|
+
callback(nested);
|
|
58
|
+
if (nested.wheres.length > 0) {
|
|
59
|
+
const sql = this.compileNestedWheres(nested);
|
|
60
|
+
this.wheres.push({ type: "raw", column: `(${sql})`, boolean, scope: undefined });
|
|
61
|
+
}
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
49
64
|
orWhere(column, operator, value) {
|
|
50
65
|
return this.where(column, operator, value, "or");
|
|
51
66
|
}
|
|
67
|
+
whereNot(column, value, boolean = "and") {
|
|
68
|
+
if (typeof column === "object" && column !== null) {
|
|
69
|
+
for (const [key, val] of Object.entries(column)) {
|
|
70
|
+
this.whereNot(key, val, boolean);
|
|
71
|
+
}
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
return this.where(column, "!=", value, boolean);
|
|
75
|
+
}
|
|
76
|
+
orWhereNot(column, value) {
|
|
77
|
+
return this.whereNot(column, value, "or");
|
|
78
|
+
}
|
|
52
79
|
whereIn(column, values, boolean = "and", scope) {
|
|
53
80
|
this.wheres.push({ type: "in", column, value: values, boolean, scope });
|
|
54
81
|
return this;
|
|
@@ -73,6 +100,36 @@ export class Builder {
|
|
|
73
100
|
this.wheres.push({ type: "between", column, value: values, boolean, operator: "NOT BETWEEN", scope });
|
|
74
101
|
return this;
|
|
75
102
|
}
|
|
103
|
+
whereDate(column, operator, value, boolean = "and") {
|
|
104
|
+
return this.addDateWhere("date", column, operator, value, boolean);
|
|
105
|
+
}
|
|
106
|
+
orWhereDate(column, operator, value) {
|
|
107
|
+
return this.whereDate(column, operator, value, "or");
|
|
108
|
+
}
|
|
109
|
+
whereDay(column, operator, value, boolean = "and") {
|
|
110
|
+
return this.addDateWhere("day", column, operator, value, boolean);
|
|
111
|
+
}
|
|
112
|
+
orWhereDay(column, operator, value) {
|
|
113
|
+
return this.whereDay(column, operator, value, "or");
|
|
114
|
+
}
|
|
115
|
+
whereMonth(column, operator, value, boolean = "and") {
|
|
116
|
+
return this.addDateWhere("month", column, operator, value, boolean);
|
|
117
|
+
}
|
|
118
|
+
orWhereMonth(column, operator, value) {
|
|
119
|
+
return this.whereMonth(column, operator, value, "or");
|
|
120
|
+
}
|
|
121
|
+
whereYear(column, operator, value, boolean = "and") {
|
|
122
|
+
return this.addDateWhere("year", column, operator, value, boolean);
|
|
123
|
+
}
|
|
124
|
+
orWhereYear(column, operator, value) {
|
|
125
|
+
return this.whereYear(column, operator, value, "or");
|
|
126
|
+
}
|
|
127
|
+
whereTime(column, operator, value, boolean = "and") {
|
|
128
|
+
return this.addDateWhere("time", column, operator, value, boolean);
|
|
129
|
+
}
|
|
130
|
+
orWhereTime(column, operator, value) {
|
|
131
|
+
return this.whereTime(column, operator, value, "or");
|
|
132
|
+
}
|
|
76
133
|
whereRaw(sql, boolean = "and", scope) {
|
|
77
134
|
this.wheres.push({ type: "raw", column: sql, boolean, scope });
|
|
78
135
|
return this;
|
|
@@ -89,6 +146,16 @@ export class Builder {
|
|
|
89
146
|
this.orders.push({ column, direction });
|
|
90
147
|
return this;
|
|
91
148
|
}
|
|
149
|
+
latest(column = "created_at") {
|
|
150
|
+
return this.orderBy(column, "desc");
|
|
151
|
+
}
|
|
152
|
+
oldest(column = "created_at") {
|
|
153
|
+
return this.orderBy(column, "asc");
|
|
154
|
+
}
|
|
155
|
+
inRandomOrder() {
|
|
156
|
+
this.randomOrderFlag = true;
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
92
159
|
groupBy(...columns) {
|
|
93
160
|
this.groups.push(...columns);
|
|
94
161
|
return this;
|
|
@@ -154,6 +221,24 @@ export class Builder {
|
|
|
154
221
|
const result = scope.call(this.model, this, ...args);
|
|
155
222
|
return (result || this);
|
|
156
223
|
}
|
|
224
|
+
when(condition, callback, defaultCallback) {
|
|
225
|
+
if (condition) {
|
|
226
|
+
const result = callback(this);
|
|
227
|
+
return (result || this);
|
|
228
|
+
}
|
|
229
|
+
else if (defaultCallback) {
|
|
230
|
+
const result = defaultCallback(this);
|
|
231
|
+
return (result || this);
|
|
232
|
+
}
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
235
|
+
unless(condition, callback, defaultCallback) {
|
|
236
|
+
return this.when(!condition, callback, defaultCallback);
|
|
237
|
+
}
|
|
238
|
+
tap(callback) {
|
|
239
|
+
const result = callback(this);
|
|
240
|
+
return (result || this);
|
|
241
|
+
}
|
|
157
242
|
has(relationName, operator = ">=", count = 1, callback) {
|
|
158
243
|
if (typeof operator === "function") {
|
|
159
244
|
callback = operator;
|
|
@@ -233,6 +318,8 @@ export class Builder {
|
|
|
233
318
|
cloned.distinctFlag = this.distinctFlag;
|
|
234
319
|
cloned.model = this.model;
|
|
235
320
|
cloned.eagerLoads = [...this.eagerLoads];
|
|
321
|
+
cloned.randomOrderFlag = this.randomOrderFlag;
|
|
322
|
+
cloned.lockMode = this.lockMode;
|
|
236
323
|
return cloned;
|
|
237
324
|
}
|
|
238
325
|
wrapColumn(value) {
|
|
@@ -269,40 +356,57 @@ export class Builder {
|
|
|
269
356
|
return value;
|
|
270
357
|
return `'${String(value).replace(/'/g, "''")}'`;
|
|
271
358
|
}
|
|
359
|
+
compileWhereClause(where, prefix) {
|
|
360
|
+
if (where.type === "basic") {
|
|
361
|
+
return `${prefix} ${this.wrap(where.column)} ${where.operator} ${this.escape(where.value)}`;
|
|
362
|
+
}
|
|
363
|
+
else if (where.type === "in") {
|
|
364
|
+
const op = where.operator === "NOT IN" ? "NOT IN" : "IN";
|
|
365
|
+
return `${prefix} ${this.wrap(where.column)} ${op} (${where.value.map((v) => this.escape(v)).join(", ")})`;
|
|
366
|
+
}
|
|
367
|
+
else if (where.type === "null") {
|
|
368
|
+
const op = where.operator === "NOT NULL" ? "IS NOT NULL" : "IS NULL";
|
|
369
|
+
return `${prefix} ${this.wrap(where.column)} ${op}`;
|
|
370
|
+
}
|
|
371
|
+
else if (where.type === "between") {
|
|
372
|
+
const op = where.operator === "NOT BETWEEN" ? "NOT BETWEEN" : "BETWEEN";
|
|
373
|
+
return `${prefix} ${this.wrap(where.column)} ${op} ${this.escape(where.value[0])} AND ${this.escape(where.value[1])}`;
|
|
374
|
+
}
|
|
375
|
+
else if (where.type === "raw") {
|
|
376
|
+
return `${prefix} ${where.column}`;
|
|
377
|
+
}
|
|
378
|
+
else if (where.type === "column") {
|
|
379
|
+
return `${prefix} ${this.wrap(where.column)} ${where.operator} ${this.wrap(where.value)}`;
|
|
380
|
+
}
|
|
381
|
+
else if (where.type === "exists") {
|
|
382
|
+
return `${prefix} ${where.operator} (${where.column})`;
|
|
383
|
+
}
|
|
384
|
+
return "";
|
|
385
|
+
}
|
|
272
386
|
compileWheres() {
|
|
273
387
|
if (this.wheres.length === 0)
|
|
274
388
|
return "";
|
|
275
389
|
const clauses = this.wheres.map((where, index) => {
|
|
276
390
|
const prefix = index === 0 ? "WHERE" : where.boolean.toUpperCase();
|
|
277
|
-
|
|
278
|
-
return `${prefix} ${this.wrap(where.column)} ${where.operator} ${this.escape(where.value)}`;
|
|
279
|
-
}
|
|
280
|
-
else if (where.type === "in") {
|
|
281
|
-
const op = where.operator === "NOT IN" ? "NOT IN" : "IN";
|
|
282
|
-
return `${prefix} ${this.wrap(where.column)} ${op} (${where.value.map((v) => this.escape(v)).join(", ")})`;
|
|
283
|
-
}
|
|
284
|
-
else if (where.type === "null") {
|
|
285
|
-
const op = where.operator === "NOT NULL" ? "IS NOT NULL" : "IS NULL";
|
|
286
|
-
return `${prefix} ${this.wrap(where.column)} ${op}`;
|
|
287
|
-
}
|
|
288
|
-
else if (where.type === "between") {
|
|
289
|
-
const op = where.operator === "NOT BETWEEN" ? "NOT BETWEEN" : "BETWEEN";
|
|
290
|
-
return `${prefix} ${this.wrap(where.column)} ${op} ${this.escape(where.value[0])} AND ${this.escape(where.value[1])}`;
|
|
291
|
-
}
|
|
292
|
-
else if (where.type === "raw") {
|
|
293
|
-
return `${prefix} ${where.column}`;
|
|
294
|
-
}
|
|
295
|
-
else if (where.type === "column") {
|
|
296
|
-
return `${prefix} ${this.wrap(where.column)} ${where.operator} ${this.wrap(where.value)}`;
|
|
297
|
-
}
|
|
298
|
-
else if (where.type === "exists") {
|
|
299
|
-
return `${prefix} ${where.operator} (${where.column})`;
|
|
300
|
-
}
|
|
301
|
-
return "";
|
|
391
|
+
return this.compileWhereClause(where, prefix);
|
|
302
392
|
});
|
|
303
393
|
return clauses.join(" ");
|
|
304
394
|
}
|
|
395
|
+
compileNestedWheres(builder) {
|
|
396
|
+
if (builder.wheres.length === 0)
|
|
397
|
+
return "";
|
|
398
|
+
const clauses = builder.wheres.map((where, index) => {
|
|
399
|
+
const prefix = index === 0 ? "" : where.boolean.toUpperCase();
|
|
400
|
+
return this.compileWhereClause(where, prefix);
|
|
401
|
+
});
|
|
402
|
+
return clauses.join(" ").trim();
|
|
403
|
+
}
|
|
305
404
|
compileOrders() {
|
|
405
|
+
if (this.randomOrderFlag) {
|
|
406
|
+
const driver = this.connection.getDriverName();
|
|
407
|
+
const fn = driver === "mysql" ? "RAND()" : "RANDOM()";
|
|
408
|
+
return `ORDER BY ${fn}`;
|
|
409
|
+
}
|
|
306
410
|
if (this.orders.length === 0)
|
|
307
411
|
return "";
|
|
308
412
|
return `ORDER BY ${this.orders.map((o) => `${this.wrap(o.column)} ${o.direction.toUpperCase()}`).join(", ")}`;
|
|
@@ -325,7 +429,10 @@ export class Builder {
|
|
|
325
429
|
compileOffset() {
|
|
326
430
|
if (this.offsetValue === undefined)
|
|
327
431
|
return "";
|
|
328
|
-
|
|
432
|
+
const limitSql = this.limitValue === undefined && this.connection.getDriverName() === "sqlite"
|
|
433
|
+
? "LIMIT -1 "
|
|
434
|
+
: "";
|
|
435
|
+
return `${limitSql}OFFSET ${this.offsetValue}`;
|
|
329
436
|
}
|
|
330
437
|
compileColumns() {
|
|
331
438
|
return this.columns.map((c) => (this.isRawColumn(c) ? c : this.wrap(c))).join(", ");
|
|
@@ -344,6 +451,8 @@ export class Builder {
|
|
|
344
451
|
sql += " " + this.compileOrders();
|
|
345
452
|
sql += " " + this.compileLimit();
|
|
346
453
|
sql += " " + this.compileOffset();
|
|
454
|
+
if (this.lockMode)
|
|
455
|
+
sql += " " + this.lockMode;
|
|
347
456
|
return sql.replace(/\s+/g, " ").trim();
|
|
348
457
|
}
|
|
349
458
|
async get() {
|
|
@@ -370,6 +479,44 @@ export class Builder {
|
|
|
370
479
|
async find(id, column = "id") {
|
|
371
480
|
return this.where(column, id).first();
|
|
372
481
|
}
|
|
482
|
+
async findOrFail(id, column = "id") {
|
|
483
|
+
const result = await this.find(id, column);
|
|
484
|
+
if (!result) {
|
|
485
|
+
throw new ModelNotFoundError(this.model?.name || "Model", id);
|
|
486
|
+
}
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
async firstOrFail() {
|
|
490
|
+
const result = await this.first();
|
|
491
|
+
if (!result) {
|
|
492
|
+
throw new ModelNotFoundError(this.model?.name || "Model");
|
|
493
|
+
}
|
|
494
|
+
return result;
|
|
495
|
+
}
|
|
496
|
+
async firstOrCreate(attributes = {}, values = {}) {
|
|
497
|
+
const found = await this.clone().where(attributes).first();
|
|
498
|
+
if (found)
|
|
499
|
+
return found;
|
|
500
|
+
if (!this.model) {
|
|
501
|
+
throw new Error("firstOrCreate requires a model to be set on the builder");
|
|
502
|
+
}
|
|
503
|
+
return this.model.create({ ...attributes, ...values });
|
|
504
|
+
}
|
|
505
|
+
async updateOrCreate(attributes, values = {}) {
|
|
506
|
+
const found = await this.clone().where(attributes).first();
|
|
507
|
+
if (found) {
|
|
508
|
+
const model = found;
|
|
509
|
+
if (typeof model.fill === "function") {
|
|
510
|
+
model.fill(values);
|
|
511
|
+
await model.save();
|
|
512
|
+
}
|
|
513
|
+
return found;
|
|
514
|
+
}
|
|
515
|
+
if (!this.model) {
|
|
516
|
+
throw new Error("updateOrCreate requires a model to be set on the builder");
|
|
517
|
+
}
|
|
518
|
+
return this.model.create({ ...attributes, ...values });
|
|
519
|
+
}
|
|
373
520
|
async pluck(column) {
|
|
374
521
|
const results = await this.select(column).get();
|
|
375
522
|
return results.map((row) => row[column]);
|
|
@@ -409,6 +556,49 @@ export class Builder {
|
|
|
409
556
|
to: total === 0 ? 0 : Math.min(page * perPage, total),
|
|
410
557
|
};
|
|
411
558
|
}
|
|
559
|
+
async chunk(count, callback) {
|
|
560
|
+
let page = 1;
|
|
561
|
+
while (true) {
|
|
562
|
+
const items = await this.clone().forPage(page, count).get();
|
|
563
|
+
if (items.length === 0)
|
|
564
|
+
break;
|
|
565
|
+
await callback(items);
|
|
566
|
+
if (items.length < count)
|
|
567
|
+
break;
|
|
568
|
+
page++;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async each(count, callback) {
|
|
572
|
+
await this.chunk(count, async (items) => {
|
|
573
|
+
for (const item of items) {
|
|
574
|
+
await callback(item);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
async *cursor() {
|
|
579
|
+
let offset = 0;
|
|
580
|
+
while (true) {
|
|
581
|
+
const items = await this.clone().offset(offset).limit(1).get();
|
|
582
|
+
if (items.length === 0)
|
|
583
|
+
break;
|
|
584
|
+
yield items[0];
|
|
585
|
+
offset++;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
async *lazy(count = 1000) {
|
|
589
|
+
let page = 1;
|
|
590
|
+
while (true) {
|
|
591
|
+
const items = await this.clone().forPage(page, count).get();
|
|
592
|
+
if (items.length === 0)
|
|
593
|
+
break;
|
|
594
|
+
for (const item of items) {
|
|
595
|
+
yield item;
|
|
596
|
+
}
|
|
597
|
+
if (items.length < count)
|
|
598
|
+
break;
|
|
599
|
+
page++;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
412
602
|
async insert(data) {
|
|
413
603
|
const records = Array.isArray(data) ? data : [data];
|
|
414
604
|
if (records.length === 0)
|
|
@@ -435,6 +625,17 @@ export class Builder {
|
|
|
435
625
|
const sql = `DELETE FROM ${this.wrap(this.tableName)} ${this.compileWheres()}`;
|
|
436
626
|
return await this.connection.run(sql.trim());
|
|
437
627
|
}
|
|
628
|
+
async increment(column, amount = 1, extra = {}) {
|
|
629
|
+
const sets = [`${this.wrap(column)} = ${this.wrap(column)} + ${amount}`];
|
|
630
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
631
|
+
sets.push(`${this.wrap(key)} = ${this.escape(value)}`);
|
|
632
|
+
}
|
|
633
|
+
const sql = `UPDATE ${this.wrap(this.tableName)} SET ${sets.join(", ")} ${this.compileWheres()}`;
|
|
634
|
+
return await this.connection.run(sql.trim());
|
|
635
|
+
}
|
|
636
|
+
async decrement(column, amount = 1, extra = {}) {
|
|
637
|
+
return this.increment(column, -amount, extra);
|
|
638
|
+
}
|
|
438
639
|
async restore() {
|
|
439
640
|
const model = this.model;
|
|
440
641
|
if (!model?.softDeletes) {
|
|
@@ -446,6 +647,87 @@ export class Builder {
|
|
|
446
647
|
const result = await this.select("1 as exists_check").limit(1).get();
|
|
447
648
|
return result.length > 0;
|
|
448
649
|
}
|
|
650
|
+
async doesntExist() {
|
|
651
|
+
return !(await this.exists());
|
|
652
|
+
}
|
|
653
|
+
take(count) {
|
|
654
|
+
return this.limit(count);
|
|
655
|
+
}
|
|
656
|
+
skip(count) {
|
|
657
|
+
return this.offset(count);
|
|
658
|
+
}
|
|
659
|
+
lockForUpdate() {
|
|
660
|
+
const driver = this.connection.getDriverName();
|
|
661
|
+
if (driver !== "sqlite") {
|
|
662
|
+
this.lockMode = driver === "mysql" ? "FOR UPDATE" : "FOR UPDATE";
|
|
663
|
+
}
|
|
664
|
+
return this;
|
|
665
|
+
}
|
|
666
|
+
sharedLock() {
|
|
667
|
+
const driver = this.connection.getDriverName();
|
|
668
|
+
if (driver === "mysql") {
|
|
669
|
+
this.lockMode = "LOCK IN SHARE MODE";
|
|
670
|
+
}
|
|
671
|
+
else if (driver === "postgres") {
|
|
672
|
+
this.lockMode = "FOR SHARE";
|
|
673
|
+
}
|
|
674
|
+
return this;
|
|
675
|
+
}
|
|
676
|
+
addDateWhere(type, column, operator, value, boolean = "and") {
|
|
677
|
+
if (value === undefined) {
|
|
678
|
+
value = operator;
|
|
679
|
+
operator = "=";
|
|
680
|
+
}
|
|
681
|
+
const driver = this.connection.getDriverName();
|
|
682
|
+
const wrapped = this.wrap(column);
|
|
683
|
+
let sql;
|
|
684
|
+
switch (type) {
|
|
685
|
+
case "date":
|
|
686
|
+
if (driver === "sqlite")
|
|
687
|
+
sql = `date(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
688
|
+
else if (driver === "mysql")
|
|
689
|
+
sql = `DATE(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
690
|
+
else
|
|
691
|
+
sql = `(${wrapped})::date ${operator} ${this.escape(value)}`;
|
|
692
|
+
break;
|
|
693
|
+
case "day":
|
|
694
|
+
if (driver === "sqlite")
|
|
695
|
+
sql = `CAST(strftime('%d', ${wrapped}) AS INTEGER) ${operator} ${this.escape(value)}`;
|
|
696
|
+
else if (driver === "mysql")
|
|
697
|
+
sql = `DAY(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
698
|
+
else
|
|
699
|
+
sql = `EXTRACT(DAY FROM ${wrapped}) ${operator} ${this.escape(value)}`;
|
|
700
|
+
break;
|
|
701
|
+
case "month":
|
|
702
|
+
if (driver === "sqlite")
|
|
703
|
+
sql = `CAST(strftime('%m', ${wrapped}) AS INTEGER) ${operator} ${this.escape(value)}`;
|
|
704
|
+
else if (driver === "mysql")
|
|
705
|
+
sql = `MONTH(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
706
|
+
else
|
|
707
|
+
sql = `EXTRACT(MONTH FROM ${wrapped}) ${operator} ${this.escape(value)}`;
|
|
708
|
+
break;
|
|
709
|
+
case "year":
|
|
710
|
+
if (driver === "sqlite")
|
|
711
|
+
sql = `CAST(strftime('%Y', ${wrapped}) AS INTEGER) ${operator} ${this.escape(value)}`;
|
|
712
|
+
else if (driver === "mysql")
|
|
713
|
+
sql = `YEAR(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
714
|
+
else
|
|
715
|
+
sql = `EXTRACT(YEAR FROM ${wrapped}) ${operator} ${this.escape(value)}`;
|
|
716
|
+
break;
|
|
717
|
+
case "time":
|
|
718
|
+
if (driver === "sqlite")
|
|
719
|
+
sql = `time(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
720
|
+
else if (driver === "mysql")
|
|
721
|
+
sql = `TIME(${wrapped}) ${operator} ${this.escape(value)}`;
|
|
722
|
+
else
|
|
723
|
+
sql = `(${wrapped})::time ${operator} ${this.escape(value)}`;
|
|
724
|
+
break;
|
|
725
|
+
default:
|
|
726
|
+
sql = `${wrapped} ${operator} ${this.escape(value)}`;
|
|
727
|
+
}
|
|
728
|
+
this.wheres.push({ type: "raw", column: sql, boolean, scope: undefined });
|
|
729
|
+
return this;
|
|
730
|
+
}
|
|
449
731
|
getModelRelation(relationName) {
|
|
450
732
|
if (!this.model) {
|
|
451
733
|
throw new Error(`Cannot query relation "${relationName}" without a model`);
|
package/package.json
CHANGED