@bunnykit/orm 0.1.27 → 0.1.29
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 +118 -5
- package/dist/src/index.d.ts +1 -1
- package/dist/src/model/Model.d.ts +19 -1
- package/dist/src/model/Model.js +157 -9
- package/dist/src/query/Builder.js +59 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -644,10 +644,10 @@ User.where("name", "Alice").dd(); // logs SQL and throws
|
|
|
644
644
|
| `each(n, fn)` | Per-item iterate |
|
|
645
645
|
| `cursor()` | Lazy async generator |
|
|
646
646
|
| `lazy(n?)` | Chunked lazy generator |
|
|
647
|
-
| `insert(data)`
|
|
648
|
-
| `insertGetId(data, col?)`
|
|
649
|
-
| `insertOrIgnore(data)`
|
|
650
|
-
| `upsert(data, uniqueBy, updateCols?)`
|
|
647
|
+
| `insert(data, options?)` | Insert row(s) with optional chunking |
|
|
648
|
+
| `insertGetId(data, col?)` | Insert and return ID |
|
|
649
|
+
| `insertOrIgnore(data)` | Insert, ignore conflicts |
|
|
650
|
+
| `upsert(data, uniqueBy, updateCols?, options?)` | Insert or update on conflict, optional chunking |
|
|
651
651
|
| `update(data)` | Update matched rows |
|
|
652
652
|
| `updateFrom(tbl, a, op, b)` | Update with JOIN |
|
|
653
653
|
| `delete()` | Delete matched rows |
|
|
@@ -739,10 +739,123 @@ const user = await User.firstOrCreate(
|
|
|
739
739
|
{ email: "alice@example.com" },
|
|
740
740
|
{ name: "Alice" },
|
|
741
741
|
);
|
|
742
|
-
const user = await User.
|
|
742
|
+
const user = await User.updateOrInsert(
|
|
743
743
|
{ email: "alice@example.com" },
|
|
744
744
|
{ name: "Alice Smith" },
|
|
745
745
|
);
|
|
746
|
+
|
|
747
|
+
// Bulk insert / upsert (apply fillable, casts, timestamps, UUID keys)
|
|
748
|
+
await User.insert([
|
|
749
|
+
{ name: "Alice", email: "alice1@example.com" },
|
|
750
|
+
{ name: "Bob", email: "alice2@example.com" },
|
|
751
|
+
], { chunkSize: 100 });
|
|
752
|
+
|
|
753
|
+
await User.upsert(
|
|
754
|
+
[{ email: "alice@example.com", name: "Alice Updated" }],
|
|
755
|
+
"email",
|
|
756
|
+
["name"],
|
|
757
|
+
{ chunkSize: 100 },
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
// Bulk create / save (fire model events by default)
|
|
761
|
+
const users = await User.createMany([
|
|
762
|
+
{ name: "Alice", email: "alice@example.com" },
|
|
763
|
+
{ name: "Bob", email: "bob@example.com" },
|
|
764
|
+
]);
|
|
765
|
+
|
|
766
|
+
await User.saveMany(users);
|
|
767
|
+
|
|
768
|
+
// Bypass observers with { events: false }
|
|
769
|
+
await User.createMany(records, { events: false });
|
|
770
|
+
await User.saveMany(models, { events: false });
|
|
771
|
+
model.save({ events: false });
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### Bulk Operations
|
|
775
|
+
|
|
776
|
+
Bunny provides bulk methods for inserting, upserting, and creating multiple records efficiently. All bulk insert and upsert operations apply fillable rules, attribute casts, timestamps, and UUID key generation automatically.
|
|
777
|
+
|
|
778
|
+
#### Model.insert(records, options?)
|
|
779
|
+
|
|
780
|
+
Insert raw records with automatic processing. Respects `fillable` guard, applies casts and timestamps, and generates UUID keys for `primaryKey = "uuid"` models.
|
|
781
|
+
|
|
782
|
+
```ts
|
|
783
|
+
await User.insert([
|
|
784
|
+
{ name: "Alice", email: "alice@example.com" },
|
|
785
|
+
{ name: "Bob", email: "bob@example.com" },
|
|
786
|
+
], { chunkSize: 500 });
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
`chunkSize` batches large inserts to avoid exceeding query size limits.
|
|
790
|
+
|
|
791
|
+
#### Model.upsert(records, uniqueBy, updateColumns?, options?)
|
|
792
|
+
|
|
793
|
+
Insert or update records based on a unique key. On insert: applies fillable, casts, timestamps, and UUID generation. On update: only the specified `updateColumns` are modified.
|
|
794
|
+
|
|
795
|
+
```ts
|
|
796
|
+
// Insert or update by email, updating only the name
|
|
797
|
+
await User.upsert(
|
|
798
|
+
[{ email: "alice@example.com", name: "Alice Updated" }],
|
|
799
|
+
"email",
|
|
800
|
+
["name"],
|
|
801
|
+
{ chunkSize: 500 },
|
|
802
|
+
);
|
|
803
|
+
|
|
804
|
+
// Insert or update by email, updating all columns except the unique key
|
|
805
|
+
await User.upsert(
|
|
806
|
+
[{ email: "alice@example.com", name: "Alice", active: true }],
|
|
807
|
+
"email",
|
|
808
|
+
);
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
#### Model.updateOrInsert(attributes, values)
|
|
812
|
+
|
|
813
|
+
Find a record by `attributes` and update with `values`, or create a new record if not found. Combines `firstOrCreate` logic with the ability to pass explicit create values.
|
|
814
|
+
|
|
815
|
+
```ts
|
|
816
|
+
const user = await User.updateOrInsert(
|
|
817
|
+
{ email: "alice@example.com" },
|
|
818
|
+
{ name: "Alice Smith", active: true },
|
|
819
|
+
);
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
#### Model.createMany(records, options?)
|
|
823
|
+
|
|
824
|
+
Create multiple model instances with full ORM support. Fires `creating` / `created` observers by default. Pass `{ events: false }` to bypass observers for better performance.
|
|
825
|
+
|
|
826
|
+
```ts
|
|
827
|
+
const users = await User.createMany([
|
|
828
|
+
{ name: "Alice", email: "alice@example.com" },
|
|
829
|
+
{ name: "Bob", email: "bob@example.com" },
|
|
830
|
+
]);
|
|
831
|
+
|
|
832
|
+
// Bypass observers for bulk create
|
|
833
|
+
await User.createMany(records, { events: false });
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
#### Model.saveMany(models, options?)
|
|
837
|
+
|
|
838
|
+
Persist an array of new or existing model instances. Existing models trigger update observers and update `updated_at`. New models trigger create observers. Auto-increment IDs are preserved when saving new models silently one-by-one where needed. Pass `{ events: false }` to bypass all observers.
|
|
839
|
+
|
|
840
|
+
```ts
|
|
841
|
+
const users = [
|
|
842
|
+
new User({ name: "Alice" }),
|
|
843
|
+
new User({ id: 5, name: "Bob" }), // existing record
|
|
844
|
+
];
|
|
845
|
+
|
|
846
|
+
await User.saveMany(users);
|
|
847
|
+
|
|
848
|
+
// Bypass observers for bulk save
|
|
849
|
+
await User.saveMany(models, { events: false });
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
#### model.save(options?)
|
|
853
|
+
|
|
854
|
+
Save an existing model instance. Pass `{ events: false }` to bypass `updating` / `updated` observers.
|
|
855
|
+
|
|
856
|
+
```ts
|
|
857
|
+
user.name = "Charlie";
|
|
858
|
+
await user.save({ events: false });
|
|
746
859
|
```
|
|
747
860
|
|
|
748
861
|
### Default Attributes
|
package/dist/src/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { MySqlGrammar } from "./schema/grammars/MySqlGrammar.js";
|
|
|
14
14
|
export { PostgresGrammar } from "./schema/grammars/PostgresGrammar.js";
|
|
15
15
|
export { Builder } from "./query/Builder.js";
|
|
16
16
|
export { Model, HasMany, BelongsTo, HasOne, HasManyThrough, HasOneThrough } from "./model/Model.js";
|
|
17
|
-
export type { ModelAttributeInput, ModelAttributes, ModelColumn, ModelColumnValue, ModelConstructor, ModelRelationName, EagerLoadConstraint, EagerLoadDefinition, EagerLoadInput, GlobalScope, CastDefinition, CastsAttributes, } from "./model/Model.js";
|
|
17
|
+
export type { ModelAttributeInput, ModelAttributes, BulkModelOptions, SaveOptions, ModelColumn, ModelColumnValue, ModelConstructor, ModelRelationName, EagerLoadConstraint, EagerLoadDefinition, EagerLoadInput, GlobalScope, CastDefinition, CastsAttributes, } from "./model/Model.js";
|
|
18
18
|
export { ModelNotFoundError } from "./model/ModelNotFoundError.js";
|
|
19
19
|
export { ObserverRegistry, type ObserverContract } from "./model/Observer.js";
|
|
20
20
|
export { MorphMap } from "./model/MorphMap.js";
|
|
@@ -19,6 +19,13 @@ export type ModelAttributes<T> = T extends {
|
|
|
19
19
|
export type ModelColumn<T> = LiteralUnion<Extract<keyof ModelAttributes<T>, string>>;
|
|
20
20
|
export type ModelColumnValue<T, K> = K extends keyof ModelAttributes<T> ? ModelAttributes<T>[K] : any;
|
|
21
21
|
export type ModelAttributeInput<T> = Partial<ModelAttributes<T>> & Record<string, any>;
|
|
22
|
+
export interface BulkModelOptions {
|
|
23
|
+
chunkSize?: number;
|
|
24
|
+
events?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface SaveOptions {
|
|
27
|
+
events?: boolean;
|
|
28
|
+
}
|
|
22
29
|
export type ModelRelationValue = Relation<any> | MorphTo<any> | MorphOne<any> | MorphMany<any> | MorphToMany<any> | BelongsToMany<any>;
|
|
23
30
|
export type ModelRelationName<T> = LiteralUnion<Extract<{
|
|
24
31
|
[K in keyof T]-?: T[K] extends (...args: any[]) => ModelRelationValue ? K : never;
|
|
@@ -132,8 +139,19 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
132
139
|
static applyGlobalScopes(builder: Builder<any>): void;
|
|
133
140
|
static getQualifiedDeletedAtColumn(): string;
|
|
134
141
|
static shouldAutoGeneratePrimaryKey(): Promise<boolean>;
|
|
142
|
+
static prepareBulkRecords<M extends ModelConstructor>(this: M, records: ModelAttributeInput<InstanceType<M>>[]): Promise<Record<string, any>[]>;
|
|
143
|
+
static prepareBulkRecord<M extends ModelConstructor>(this: M, record: ModelAttributeInput<InstanceType<M>>, options?: {
|
|
144
|
+
touchCreatedAt?: boolean;
|
|
145
|
+
touchUpdatedAt?: boolean;
|
|
146
|
+
generatePrimaryKey?: boolean;
|
|
147
|
+
}): Promise<Record<string, any>>;
|
|
135
148
|
static hydrate<M extends ModelConstructor>(this: M, row: Record<string, any>, connection?: Connection): InstanceType<M>;
|
|
136
149
|
static create<M extends ModelConstructor>(this: M, attributes: ModelAttributeInput<InstanceType<M>>): Promise<InstanceType<M>>;
|
|
150
|
+
static insert<M extends ModelConstructor>(this: M, records: ModelAttributeInput<InstanceType<M>> | ModelAttributeInput<InstanceType<M>>[], options?: Omit<BulkModelOptions, "events">): Promise<any>;
|
|
151
|
+
static upsert<M extends ModelConstructor>(this: M, records: ModelAttributeInput<InstanceType<M>> | ModelAttributeInput<InstanceType<M>>[], uniqueBy: ModelColumn<InstanceType<M>> | ModelColumn<InstanceType<M>>[], updateColumns?: ModelColumn<InstanceType<M>>[], options?: Omit<BulkModelOptions, "events">): Promise<any>;
|
|
152
|
+
static updateOrInsert<M extends ModelConstructor>(this: M, attributes: ModelAttributeInput<InstanceType<M>>, values?: ModelAttributeInput<InstanceType<M>>): Promise<boolean>;
|
|
153
|
+
static createMany<M extends ModelConstructor>(this: M, records: ModelAttributeInput<InstanceType<M>>[], options?: BulkModelOptions): Promise<InstanceType<M>[]>;
|
|
154
|
+
static saveMany<M extends ModelConstructor>(this: M, models: InstanceType<M>[], options?: BulkModelOptions): Promise<InstanceType<M>[]>;
|
|
137
155
|
static find<M extends ModelConstructor>(this: M, id: any): Promise<InstanceType<M> | null>;
|
|
138
156
|
static findOrFail<M extends ModelConstructor>(this: M, id: any): Promise<InstanceType<M>>;
|
|
139
157
|
static first<M extends ModelConstructor>(this: M): Promise<InstanceType<M> | null>;
|
|
@@ -212,7 +230,7 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
212
230
|
protected resolveCustomCast(cast: CastDefinition): CastsAttributes | null;
|
|
213
231
|
getDirty(): Partial<T>;
|
|
214
232
|
isDirty(): boolean;
|
|
215
|
-
save(): Promise<this>;
|
|
233
|
+
save(options?: SaveOptions): Promise<this>;
|
|
216
234
|
updateTimestamps(): void;
|
|
217
235
|
touch(): Promise<boolean>;
|
|
218
236
|
increment<K extends ModelColumn<this>>(column: K, amount?: number, extra?: ModelAttributeInput<this>): Promise<this>;
|
package/dist/src/model/Model.js
CHANGED
|
@@ -433,6 +433,51 @@ export class Model {
|
|
|
433
433
|
const numericTypes = new Set(["integer", "int", "bigint", "smallint", "tinyint", "real", "float", "double", "decimal", "numeric"]);
|
|
434
434
|
return !numericTypes.has(type);
|
|
435
435
|
}
|
|
436
|
+
static async prepareBulkRecords(records) {
|
|
437
|
+
const generatePk = await this.shouldAutoGeneratePrimaryKey();
|
|
438
|
+
const now = this.timestamps ? new Date().toISOString() : null;
|
|
439
|
+
const prepared = [];
|
|
440
|
+
for (const record of records) {
|
|
441
|
+
const instance = new this();
|
|
442
|
+
instance.fill(record);
|
|
443
|
+
const attributes = { ...instance.$attributes };
|
|
444
|
+
if (now) {
|
|
445
|
+
if (attributes.created_at === undefined)
|
|
446
|
+
attributes.created_at = now;
|
|
447
|
+
if (attributes.updated_at === undefined)
|
|
448
|
+
attributes.updated_at = now;
|
|
449
|
+
}
|
|
450
|
+
if (generatePk) {
|
|
451
|
+
const pk = this.primaryKey;
|
|
452
|
+
const pkValue = attributes[pk];
|
|
453
|
+
if (pkValue === null || pkValue === undefined || pkValue === "") {
|
|
454
|
+
attributes[pk] = crypto.randomUUID();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
prepared.push(attributes);
|
|
458
|
+
}
|
|
459
|
+
return prepared;
|
|
460
|
+
}
|
|
461
|
+
static async prepareBulkRecord(record, options = {}) {
|
|
462
|
+
const instance = new this();
|
|
463
|
+
instance.fill(record);
|
|
464
|
+
const attributes = { ...instance.$attributes };
|
|
465
|
+
if (this.timestamps) {
|
|
466
|
+
const now = instance.freshTimestamp();
|
|
467
|
+
if (options.touchCreatedAt !== false && attributes.created_at === undefined)
|
|
468
|
+
attributes.created_at = now;
|
|
469
|
+
if (options.touchUpdatedAt !== false && attributes.updated_at === undefined)
|
|
470
|
+
attributes.updated_at = now;
|
|
471
|
+
}
|
|
472
|
+
if (options.generatePrimaryKey !== false) {
|
|
473
|
+
const primaryKey = this.primaryKey;
|
|
474
|
+
const primaryKeyValue = attributes[primaryKey];
|
|
475
|
+
if ((primaryKeyValue === null || primaryKeyValue === undefined || primaryKeyValue === "") && await this.shouldAutoGeneratePrimaryKey()) {
|
|
476
|
+
attributes[primaryKey] = crypto.randomUUID();
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return attributes;
|
|
480
|
+
}
|
|
436
481
|
static hydrate(row, connection) {
|
|
437
482
|
const instance = new this();
|
|
438
483
|
instance.$attributes = { ...instance.$attributes, ...row };
|
|
@@ -450,6 +495,100 @@ export class Model {
|
|
|
450
495
|
await instance.save();
|
|
451
496
|
return instance;
|
|
452
497
|
}
|
|
498
|
+
static async insert(records, options = {}) {
|
|
499
|
+
const prepared = await this.prepareBulkRecords(Array.isArray(records) ? records : [records]);
|
|
500
|
+
const chunkSize = options.chunkSize || prepared.length || 1;
|
|
501
|
+
let result;
|
|
502
|
+
for (let i = 0; i < prepared.length; i += chunkSize) {
|
|
503
|
+
result = await this.query().insert(prepared.slice(i, i + chunkSize));
|
|
504
|
+
}
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
static async upsert(records, uniqueBy, updateColumns, options = {}) {
|
|
508
|
+
const prepared = await this.prepareBulkRecords(Array.isArray(records) ? records : [records]);
|
|
509
|
+
const chunkSize = options.chunkSize || prepared.length || 1;
|
|
510
|
+
let columns = updateColumns;
|
|
511
|
+
if (!columns && this.timestamps) {
|
|
512
|
+
const uniqueColumns = new Set(Array.isArray(uniqueBy) ? uniqueBy : [uniqueBy]);
|
|
513
|
+
columns = Object.keys(prepared[0] || {}).filter((column) => column !== "created_at" && !uniqueColumns.has(column));
|
|
514
|
+
}
|
|
515
|
+
let result;
|
|
516
|
+
for (let i = 0; i < prepared.length; i += chunkSize) {
|
|
517
|
+
result = await this.query().upsert(prepared.slice(i, i + chunkSize), uniqueBy, columns);
|
|
518
|
+
}
|
|
519
|
+
return result;
|
|
520
|
+
}
|
|
521
|
+
static async updateOrInsert(attributes, values = {}) {
|
|
522
|
+
const exists = await this.where(attributes).exists();
|
|
523
|
+
if (exists) {
|
|
524
|
+
const update = await this.prepareBulkRecord(values, { touchUpdatedAt: true, touchCreatedAt: false, generatePrimaryKey: false });
|
|
525
|
+
await this.where(attributes).update(update);
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
await this.insert({ ...attributes, ...values });
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
static async createMany(records, options = {}) {
|
|
532
|
+
const models = records.map((attributes) => new this(attributes));
|
|
533
|
+
await this.saveMany(models, options);
|
|
534
|
+
return models;
|
|
535
|
+
}
|
|
536
|
+
static async saveMany(models, options = {}) {
|
|
537
|
+
const chunkSize = options.chunkSize || models.length || 1;
|
|
538
|
+
const events = options.events !== false;
|
|
539
|
+
if (events) {
|
|
540
|
+
for (const model of models) {
|
|
541
|
+
await model.save();
|
|
542
|
+
}
|
|
543
|
+
return models;
|
|
544
|
+
}
|
|
545
|
+
for (let i = 0; i < models.length; i += chunkSize) {
|
|
546
|
+
const chunk = models.slice(i, i + chunkSize);
|
|
547
|
+
const newModels = chunk.filter((model) => !model.$exists);
|
|
548
|
+
const existingModels = chunk.filter((model) => model.$exists);
|
|
549
|
+
if (newModels.length > 0) {
|
|
550
|
+
const shouldGeneratePrimaryKey = await this.shouldAutoGeneratePrimaryKey();
|
|
551
|
+
const bulkModels = [];
|
|
552
|
+
for (const model of newModels) {
|
|
553
|
+
const pk = model.getAttribute(this.primaryKey);
|
|
554
|
+
if (!shouldGeneratePrimaryKey && (pk === null || pk === undefined || pk === "")) {
|
|
555
|
+
const record = await this.prepareBulkRecord(model.$attributes);
|
|
556
|
+
const id = await this.query().insertGetId(record);
|
|
557
|
+
if (id)
|
|
558
|
+
record[this.primaryKey] = id;
|
|
559
|
+
model.$attributes = record;
|
|
560
|
+
model.$original = { ...record };
|
|
561
|
+
model.$exists = true;
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
bulkModels.push(model);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (bulkModels.length > 0) {
|
|
568
|
+
const records = await this.prepareBulkRecords(bulkModels.map((model) => model.$attributes));
|
|
569
|
+
await this.query().insert(records);
|
|
570
|
+
for (let index = 0; index < bulkModels.length; index++) {
|
|
571
|
+
bulkModels[index].$attributes = records[index];
|
|
572
|
+
bulkModels[index].$original = { ...records[index] };
|
|
573
|
+
bulkModels[index].$exists = true;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
for (const model of existingModels) {
|
|
578
|
+
let dirty = model.getDirty();
|
|
579
|
+
if (Object.keys(dirty).length > 0 && this.timestamps) {
|
|
580
|
+
model.$attributes.updated_at = model.freshTimestamp();
|
|
581
|
+
delete model.$castCache.updated_at;
|
|
582
|
+
dirty = model.getDirty();
|
|
583
|
+
}
|
|
584
|
+
if (Object.keys(dirty).length === 0)
|
|
585
|
+
continue;
|
|
586
|
+
await this.query().where(this.primaryKey, model.getAttribute(this.primaryKey)).update(dirty);
|
|
587
|
+
model.$original = { ...model.$attributes };
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return models;
|
|
591
|
+
}
|
|
453
592
|
static async find(id) {
|
|
454
593
|
return this.query().find(id, this.primaryKey);
|
|
455
594
|
}
|
|
@@ -836,10 +975,12 @@ export class Model {
|
|
|
836
975
|
isDirty() {
|
|
837
976
|
return Object.keys(this.getDirty()).length > 0;
|
|
838
977
|
}
|
|
839
|
-
async save() {
|
|
978
|
+
async save(options = {}) {
|
|
840
979
|
const constructor = this.constructor;
|
|
980
|
+
const events = options.events !== false;
|
|
841
981
|
if (this.$exists) {
|
|
842
|
-
|
|
982
|
+
if (events)
|
|
983
|
+
await ObserverRegistry.dispatch("saving", this);
|
|
843
984
|
let dirty = this.getDirty();
|
|
844
985
|
if (Object.keys(dirty).length > 0 && constructor.timestamps) {
|
|
845
986
|
this.$attributes["updated_at"] = this.freshTimestamp();
|
|
@@ -847,20 +988,25 @@ export class Model {
|
|
|
847
988
|
dirty = this.getDirty();
|
|
848
989
|
}
|
|
849
990
|
if (Object.keys(dirty).length > 0) {
|
|
850
|
-
|
|
991
|
+
if (events)
|
|
992
|
+
await ObserverRegistry.dispatch("updating", this);
|
|
851
993
|
const pk = this.getAttribute(constructor.primaryKey);
|
|
852
994
|
const connection = this.getConnection();
|
|
853
995
|
await new Builder(connection, connection.qualifyTable(constructor.getTable()))
|
|
854
996
|
.where(constructor.primaryKey, pk)
|
|
855
997
|
.update(dirty);
|
|
856
|
-
|
|
998
|
+
if (events)
|
|
999
|
+
await ObserverRegistry.dispatch("updated", this);
|
|
857
1000
|
}
|
|
858
1001
|
this.$original = { ...this.$attributes };
|
|
859
|
-
|
|
1002
|
+
if (events)
|
|
1003
|
+
await ObserverRegistry.dispatch("saved", this);
|
|
860
1004
|
}
|
|
861
1005
|
else {
|
|
862
|
-
|
|
863
|
-
|
|
1006
|
+
if (events)
|
|
1007
|
+
await ObserverRegistry.dispatch("creating", this);
|
|
1008
|
+
if (events)
|
|
1009
|
+
await ObserverRegistry.dispatch("saving", this);
|
|
864
1010
|
if (constructor.timestamps) {
|
|
865
1011
|
const now = this.freshTimestamp();
|
|
866
1012
|
this.$attributes["created_at"] = now;
|
|
@@ -889,8 +1035,10 @@ export class Model {
|
|
|
889
1035
|
}
|
|
890
1036
|
this.$exists = true;
|
|
891
1037
|
this.$original = { ...this.$attributes };
|
|
892
|
-
|
|
893
|
-
|
|
1038
|
+
if (events)
|
|
1039
|
+
await ObserverRegistry.dispatch("created", this);
|
|
1040
|
+
if (events)
|
|
1041
|
+
await ObserverRegistry.dispatch("saved", this);
|
|
894
1042
|
}
|
|
895
1043
|
const identityMap = IdentityMap.current();
|
|
896
1044
|
if (identityMap) {
|
|
@@ -718,8 +718,7 @@ export class Builder {
|
|
|
718
718
|
return rows;
|
|
719
719
|
}
|
|
720
720
|
async first() {
|
|
721
|
-
|
|
722
|
-
return results[0] || null;
|
|
721
|
+
return (await this.limit(1).get())[0] || null;
|
|
723
722
|
}
|
|
724
723
|
async find(id, column = "id") {
|
|
725
724
|
return this.where(column, id).first();
|
|
@@ -773,8 +772,15 @@ export class Builder {
|
|
|
773
772
|
return instance;
|
|
774
773
|
}
|
|
775
774
|
async pluck(column) {
|
|
776
|
-
const
|
|
777
|
-
|
|
775
|
+
const model = this.model;
|
|
776
|
+
this.model = undefined;
|
|
777
|
+
this.bindings = [];
|
|
778
|
+
this.parameterize = true;
|
|
779
|
+
const sql = this.select(column).toSql();
|
|
780
|
+
this.parameterize = false;
|
|
781
|
+
const rows = await this.connection.query(sql, this.bindings);
|
|
782
|
+
this.model = model;
|
|
783
|
+
return Array.from(rows).map((row) => row[column]);
|
|
778
784
|
}
|
|
779
785
|
async aggregate(sql, alias) {
|
|
780
786
|
const query = this.clone();
|
|
@@ -790,19 +796,55 @@ export class Builder {
|
|
|
790
796
|
return result ? result[alias] : null;
|
|
791
797
|
}
|
|
792
798
|
async count(column = "*") {
|
|
793
|
-
|
|
799
|
+
const countSql = column === "*" ? "COUNT(*)" : `COUNT(${this.grammar.wrap(column)})`;
|
|
800
|
+
this.bindings = [];
|
|
801
|
+
this.parameterize = true;
|
|
802
|
+
const from = this.fromRaw || this.grammar.wrap(this.tableName);
|
|
803
|
+
const whereSql = this.compileWheres();
|
|
804
|
+
this.parameterize = false;
|
|
805
|
+
const sql = `SELECT ${countSql} as cnt FROM ${from}${whereSql ? " " + whereSql : ""}`;
|
|
806
|
+
const rows = await this.connection.query(sql, this.bindings);
|
|
807
|
+
return rows.length > 0 ? Number(rows[0].cnt) : 0;
|
|
794
808
|
}
|
|
795
809
|
async sum(column) {
|
|
796
|
-
|
|
810
|
+
const sql = `SELECT SUM(${this.grammar.wrap(column)}) as sum_val FROM ${this.fromRaw || this.grammar.wrap(this.tableName)}`;
|
|
811
|
+
this.bindings = [];
|
|
812
|
+
this.parameterize = true;
|
|
813
|
+
const whereSql = this.compileWheres();
|
|
814
|
+
this.parameterize = false;
|
|
815
|
+
const fullSql = whereSql ? `${sql}${whereSql}` : sql;
|
|
816
|
+
const rows = await this.connection.query(fullSql, this.bindings);
|
|
817
|
+
return rows.length > 0 ? Number(rows[0].sum_val || 0) : 0;
|
|
797
818
|
}
|
|
798
819
|
async avg(column) {
|
|
799
|
-
|
|
820
|
+
const sql = `SELECT AVG(${this.grammar.wrap(column)}) as avg_val FROM ${this.fromRaw || this.grammar.wrap(this.tableName)}`;
|
|
821
|
+
this.bindings = [];
|
|
822
|
+
this.parameterize = true;
|
|
823
|
+
const whereSql = this.compileWheres();
|
|
824
|
+
this.parameterize = false;
|
|
825
|
+
const fullSql = whereSql ? `${sql}${whereSql}` : sql;
|
|
826
|
+
const rows = await this.connection.query(fullSql, this.bindings);
|
|
827
|
+
return rows.length > 0 ? Number(rows[0].avg_val || 0) : 0;
|
|
800
828
|
}
|
|
801
829
|
async min(column) {
|
|
802
|
-
|
|
830
|
+
const sql = `SELECT MIN(${this.grammar.wrap(column)}) as min_val FROM ${this.fromRaw || this.grammar.wrap(this.tableName)}`;
|
|
831
|
+
this.bindings = [];
|
|
832
|
+
this.parameterize = true;
|
|
833
|
+
const whereSql = this.compileWheres();
|
|
834
|
+
this.parameterize = false;
|
|
835
|
+
const fullSql = whereSql ? `${sql}${whereSql}` : sql;
|
|
836
|
+
const rows = await this.connection.query(fullSql, this.bindings);
|
|
837
|
+
return rows.length > 0 ? rows[0].min_val : null;
|
|
803
838
|
}
|
|
804
839
|
async max(column) {
|
|
805
|
-
|
|
840
|
+
const sql = `SELECT MAX(${this.grammar.wrap(column)}) as max_val FROM ${this.fromRaw || this.grammar.wrap(this.tableName)}`;
|
|
841
|
+
this.bindings = [];
|
|
842
|
+
this.parameterize = true;
|
|
843
|
+
const whereSql = this.compileWheres();
|
|
844
|
+
this.parameterize = false;
|
|
845
|
+
const fullSql = whereSql ? `${sql}${whereSql}` : sql;
|
|
846
|
+
const rows = await this.connection.query(fullSql, this.bindings);
|
|
847
|
+
return rows.length > 0 ? rows[0].max_val : null;
|
|
806
848
|
}
|
|
807
849
|
async paginate(perPage = 15, page = 1) {
|
|
808
850
|
const countQuery = this.clone();
|
|
@@ -1040,8 +1082,14 @@ export class Builder {
|
|
|
1040
1082
|
return this.withTrashed().update({ [model.deletedAtColumn]: null });
|
|
1041
1083
|
}
|
|
1042
1084
|
async exists() {
|
|
1043
|
-
|
|
1044
|
-
|
|
1085
|
+
this.bindings = [];
|
|
1086
|
+
this.parameterize = true;
|
|
1087
|
+
const from = this.fromRaw || this.grammar.wrap(this.tableName);
|
|
1088
|
+
const whereSql = this.compileWheres();
|
|
1089
|
+
this.parameterize = false;
|
|
1090
|
+
const sql = `SELECT 1 FROM ${from}${whereSql ? whereSql : ""} LIMIT 1`;
|
|
1091
|
+
const rows = await this.connection.query(sql, this.bindings);
|
|
1092
|
+
return rows.length > 0;
|
|
1045
1093
|
}
|
|
1046
1094
|
async doesntExist() {
|
|
1047
1095
|
return !(await this.exists());
|
package/package.json
CHANGED