@getstrata/core 0.5.97 → 0.5.98
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/CHANGELOG.md +10 -0
- package/dist/core/contracts/container.d.ts +2 -0
- package/dist/core/database/factory.d.ts +31 -4
- package/dist/core/database/index.d.ts +3 -1
- package/dist/core/database/inflection.d.ts +4 -0
- package/dist/core/database/model.d.ts +50 -1
- package/dist/core/database/relationQuery.d.ts +149 -0
- package/dist/core/database/repositoryQuery.d.ts +5 -0
- package/dist/core/database/whereBuilder.d.ts +9 -1
- package/dist/core/events/eventBus.d.ts +2 -0
- package/dist/core/http/resources.d.ts +20 -1
- package/dist/entries/contracts/container.js +6 -0
- package/dist/entries/database/factory.js +116 -5
- package/dist/entries/database/model.js +760 -7
- package/dist/entries/database/query.js +9 -0
- package/dist/entries/database/repositoryQuery.js +26 -0
- package/dist/entries/database/schema.js +9 -0
- package/dist/entries/http/resources.js +62 -1
- package/dist/framework/public-api.d.ts +4 -2
- package/dist/index.js +990 -7
- package/package.json +1 -1
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
// ../../src/core/database/model.ts
|
|
3
3
|
import { NotFoundError } from "@getstrata/core/errors/http";
|
|
4
4
|
|
|
5
|
+
// ../../src/core/database/inflection.ts
|
|
6
|
+
function singularize(word) {
|
|
7
|
+
if (word.endsWith("ies") && word.length > 3) {
|
|
8
|
+
return `${word.slice(0, -3)}y`;
|
|
9
|
+
}
|
|
10
|
+
if (/(ses|xes|zes|ches|shes)$/i.test(word)) {
|
|
11
|
+
return word.slice(0, -2);
|
|
12
|
+
}
|
|
13
|
+
if (word.endsWith("s") && !word.endsWith("ss")) {
|
|
14
|
+
return word.slice(0, -1);
|
|
15
|
+
}
|
|
16
|
+
return word;
|
|
17
|
+
}
|
|
18
|
+
function foreignKeyFromTable(tableName) {
|
|
19
|
+
return `${singularize(tableName)}_id`;
|
|
20
|
+
}
|
|
21
|
+
function pivotTableName(leftTable, rightTable) {
|
|
22
|
+
return [singularize(leftTable), singularize(rightTable)].sort().join("_");
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
// ../../src/core/database/query.ts
|
|
6
26
|
function quoteIdentifier(identifier) {
|
|
7
27
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
|
|
@@ -115,7 +135,16 @@ function buildWhereClause(tableName, where = {}) {
|
|
|
115
135
|
params
|
|
116
136
|
};
|
|
117
137
|
}
|
|
138
|
+
function remapExistsSql(sql, existsParams, params) {
|
|
139
|
+
const offset = params.length;
|
|
140
|
+
params.push(...existsParams);
|
|
141
|
+
return sql.replace(/\$(\d+)/g, (_match, index) => `$${offset + Number(index)}`);
|
|
142
|
+
}
|
|
118
143
|
function buildWhereNodeClause(tableName, node, params) {
|
|
144
|
+
if ("exists" in node) {
|
|
145
|
+
const body = remapExistsSql(node.exists.sql, node.exists.params, params);
|
|
146
|
+
return `${node.exists.not ? "NOT " : ""}EXISTS (${body})`;
|
|
147
|
+
}
|
|
119
148
|
if ("where" in node) {
|
|
120
149
|
return appendWhereParts(tableName, node.where, params);
|
|
121
150
|
}
|
|
@@ -416,6 +445,428 @@ function buildDeleteByIdQuery(table, id) {
|
|
|
416
445
|
};
|
|
417
446
|
}
|
|
418
447
|
|
|
448
|
+
// ../../src/core/database/relationQuery.ts
|
|
449
|
+
function asWhere(where) {
|
|
450
|
+
return where;
|
|
451
|
+
}
|
|
452
|
+
function ownerId(owner, ownerKey) {
|
|
453
|
+
if (typeof owner.get === "function") {
|
|
454
|
+
return owner.get(ownerKey);
|
|
455
|
+
}
|
|
456
|
+
if (owner && typeof owner === "object" && "id" in owner && owner.id !== undefined) {
|
|
457
|
+
return owner.id;
|
|
458
|
+
}
|
|
459
|
+
throw new Error("belongsTo.associate() requires a related model or { id }.");
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
class HasManyRelationQuery {
|
|
463
|
+
parent;
|
|
464
|
+
related;
|
|
465
|
+
relation;
|
|
466
|
+
kind = "hasMany";
|
|
467
|
+
extraWhere = {};
|
|
468
|
+
extraOptions = {};
|
|
469
|
+
constructor(parent, related, relation) {
|
|
470
|
+
this.parent = parent;
|
|
471
|
+
this.related = related;
|
|
472
|
+
this.relation = relation;
|
|
473
|
+
}
|
|
474
|
+
where(where) {
|
|
475
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
476
|
+
return this;
|
|
477
|
+
}
|
|
478
|
+
orderBy(orderBy) {
|
|
479
|
+
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
480
|
+
return this;
|
|
481
|
+
}
|
|
482
|
+
limit(limit) {
|
|
483
|
+
this.extraOptions = { ...this.extraOptions, limit };
|
|
484
|
+
return this;
|
|
485
|
+
}
|
|
486
|
+
applyEagerLoad(query, alias) {
|
|
487
|
+
query.withHasMany(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
488
|
+
}
|
|
489
|
+
hydrateEager(row, alias) {
|
|
490
|
+
const value = row[alias] ?? [];
|
|
491
|
+
const rows = Array.isArray(value) ? value : [];
|
|
492
|
+
return rows.map((item) => this.related.newFromRecord(item));
|
|
493
|
+
}
|
|
494
|
+
toExistsClause(parentTable) {
|
|
495
|
+
const childTable = this.related.repository().getTable().name;
|
|
496
|
+
const extra = buildAdvancedWhereClause(childTable, this.extraWhere, [], []);
|
|
497
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
498
|
+
const sql = `SELECT 1 FROM ${quoteIdentifier(childTable)} WHERE ${qualifyColumn(childTable, this.relation.foreignKey)} = ${qualifyColumn(parentTable, this.relation.localKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
|
|
499
|
+
return { sql, params: extra.params };
|
|
500
|
+
}
|
|
501
|
+
scopedQuery() {
|
|
502
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
503
|
+
let query = repository.query(asWhere({
|
|
504
|
+
[this.relation.foreignKey]: this.parent.get(this.relation.localKey),
|
|
505
|
+
...this.extraWhere
|
|
506
|
+
}));
|
|
507
|
+
if (this.extraOptions.orderBy) {
|
|
508
|
+
query = query.orderBy(this.extraOptions.orderBy);
|
|
509
|
+
}
|
|
510
|
+
if (this.extraOptions.limit !== undefined) {
|
|
511
|
+
query = query.limit(this.extraOptions.limit);
|
|
512
|
+
}
|
|
513
|
+
return query;
|
|
514
|
+
}
|
|
515
|
+
async get() {
|
|
516
|
+
const rows = await this.scopedQuery().get();
|
|
517
|
+
return rows.map((row) => this.related.newFromRecord(row));
|
|
518
|
+
}
|
|
519
|
+
async first() {
|
|
520
|
+
const rows = await this.limit(1).get();
|
|
521
|
+
return rows[0] ?? null;
|
|
522
|
+
}
|
|
523
|
+
async count() {
|
|
524
|
+
return (await this.get()).length;
|
|
525
|
+
}
|
|
526
|
+
async create(attributes = {}) {
|
|
527
|
+
return this.related.create(attributes, {
|
|
528
|
+
[this.relation.foreignKey]: this.parent.get(this.relation.localKey)
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
async createMany(records) {
|
|
532
|
+
const created = [];
|
|
533
|
+
for (const attributes of records) {
|
|
534
|
+
created.push(await this.create(attributes));
|
|
535
|
+
}
|
|
536
|
+
return created;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
class HasOneRelationQuery {
|
|
541
|
+
relation;
|
|
542
|
+
kind = "hasOne";
|
|
543
|
+
inner;
|
|
544
|
+
constructor(parent, related, relation) {
|
|
545
|
+
this.relation = relation;
|
|
546
|
+
this.inner = new HasManyRelationQuery(parent, related, {
|
|
547
|
+
type: "hasMany",
|
|
548
|
+
name: relation.name,
|
|
549
|
+
localKey: relation.localKey,
|
|
550
|
+
foreignKey: relation.foreignKey
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
where(where) {
|
|
554
|
+
this.inner.where(where);
|
|
555
|
+
return this;
|
|
556
|
+
}
|
|
557
|
+
orderBy(orderBy) {
|
|
558
|
+
this.inner.orderBy(orderBy);
|
|
559
|
+
return this;
|
|
560
|
+
}
|
|
561
|
+
applyEagerLoad(query, alias) {
|
|
562
|
+
this.inner.limit(1).applyEagerLoad(query, alias);
|
|
563
|
+
}
|
|
564
|
+
hydrateEager(row, alias) {
|
|
565
|
+
const hydrated = this.inner.hydrateEager(row, alias);
|
|
566
|
+
return hydrated[0];
|
|
567
|
+
}
|
|
568
|
+
toExistsClause(parentTable) {
|
|
569
|
+
return this.inner.toExistsClause(parentTable);
|
|
570
|
+
}
|
|
571
|
+
async get() {
|
|
572
|
+
return this.inner.limit(1).first();
|
|
573
|
+
}
|
|
574
|
+
async first() {
|
|
575
|
+
return this.get();
|
|
576
|
+
}
|
|
577
|
+
async count() {
|
|
578
|
+
return await this.get() ? 1 : 0;
|
|
579
|
+
}
|
|
580
|
+
async create(attributes = {}) {
|
|
581
|
+
return this.inner.create(attributes);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
class BelongsToRelationQuery {
|
|
586
|
+
parent;
|
|
587
|
+
related;
|
|
588
|
+
relation;
|
|
589
|
+
kind = "belongsTo";
|
|
590
|
+
extraOptions = {};
|
|
591
|
+
constructor(parent, related, relation) {
|
|
592
|
+
this.parent = parent;
|
|
593
|
+
this.related = related;
|
|
594
|
+
this.relation = relation;
|
|
595
|
+
}
|
|
596
|
+
orderBy(orderBy) {
|
|
597
|
+
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
598
|
+
return this;
|
|
599
|
+
}
|
|
600
|
+
applyEagerLoad(query, alias) {
|
|
601
|
+
query.withBelongsTo(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
602
|
+
}
|
|
603
|
+
hydrateEager(row, alias) {
|
|
604
|
+
const value = row[alias];
|
|
605
|
+
return value ? this.related.newFromRecord(value) : value;
|
|
606
|
+
}
|
|
607
|
+
toExistsClause(parentTable) {
|
|
608
|
+
const relatedTable = this.related.repository().getTable().name;
|
|
609
|
+
return {
|
|
610
|
+
sql: `SELECT 1 FROM ${quoteIdentifier(relatedTable)} WHERE ${qualifyColumn(relatedTable, this.relation.ownerKey)} = ${qualifyColumn(parentTable, this.relation.foreignKey)}`,
|
|
611
|
+
params: []
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
async get() {
|
|
615
|
+
const foreign = this.parent.get(this.relation.foreignKey);
|
|
616
|
+
if (foreign === null || foreign === undefined) {
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
620
|
+
let query = repository.query(asWhere({ [this.relation.ownerKey]: foreign }));
|
|
621
|
+
if (this.extraOptions.orderBy) {
|
|
622
|
+
query = query.orderBy(this.extraOptions.orderBy);
|
|
623
|
+
}
|
|
624
|
+
const row = await query.first();
|
|
625
|
+
return row ? this.related.newFromRecord(row) : null;
|
|
626
|
+
}
|
|
627
|
+
async first() {
|
|
628
|
+
return this.get();
|
|
629
|
+
}
|
|
630
|
+
async associate(owner) {
|
|
631
|
+
await this.parent.getRepository().updateById(this.parent.id, {
|
|
632
|
+
[this.relation.foreignKey]: ownerId(owner, this.relation.ownerKey)
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
async dissociate() {
|
|
636
|
+
await this.parent.getRepository().updateById(this.parent.id, {
|
|
637
|
+
[this.relation.foreignKey]: null
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
class BelongsToManyRelationQuery {
|
|
643
|
+
parent;
|
|
644
|
+
related;
|
|
645
|
+
relation;
|
|
646
|
+
kind = "belongsToMany";
|
|
647
|
+
extraWhere = {};
|
|
648
|
+
extraOptions = {};
|
|
649
|
+
constructor(parent, related, relation) {
|
|
650
|
+
this.parent = parent;
|
|
651
|
+
this.related = related;
|
|
652
|
+
this.relation = relation;
|
|
653
|
+
}
|
|
654
|
+
where(where) {
|
|
655
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
656
|
+
return this;
|
|
657
|
+
}
|
|
658
|
+
orderBy(orderBy) {
|
|
659
|
+
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
660
|
+
return this;
|
|
661
|
+
}
|
|
662
|
+
applyEagerLoad(_query, _alias) {}
|
|
663
|
+
hydrateEager(row, alias) {
|
|
664
|
+
const value = row[alias] ?? [];
|
|
665
|
+
const rows = Array.isArray(value) ? value : [];
|
|
666
|
+
return rows.map((item) => this.related.newFromRecord(item));
|
|
667
|
+
}
|
|
668
|
+
toExistsClause(parentTable) {
|
|
669
|
+
const relatedTable = this.related.repository().getTable().name;
|
|
670
|
+
const extra = buildAdvancedWhereClause(relatedTable, this.extraWhere, [], []);
|
|
671
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
672
|
+
const sql = `SELECT 1 FROM ${quoteIdentifier(relatedTable)} INNER JOIN ${quoteIdentifier(this.relation.pivotTable)} ON ${qualifyColumn(this.relation.pivotTable, this.relation.relatedPivotKey)} = ${qualifyColumn(relatedTable, this.relation.relatedKey)} WHERE ${qualifyColumn(this.relation.pivotTable, this.relation.foreignPivotKey)} = ${qualifyColumn(parentTable, this.relation.parentKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
|
|
673
|
+
return { sql, params: extra.params };
|
|
674
|
+
}
|
|
675
|
+
connection() {
|
|
676
|
+
return this.parent.getRepository().getConnection();
|
|
677
|
+
}
|
|
678
|
+
async get() {
|
|
679
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
680
|
+
const pivotRows = await this.connection().unsafe(`SELECT * FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1`, [parentId]);
|
|
681
|
+
if (pivotRows.length === 0) {
|
|
682
|
+
return [];
|
|
683
|
+
}
|
|
684
|
+
const relatedIds = [
|
|
685
|
+
...new Set(pivotRows.map((row) => row[this.relation.relatedPivotKey]))
|
|
686
|
+
];
|
|
687
|
+
const repository = this.related.repository().withConnection(this.connection());
|
|
688
|
+
const rows = await repository.findAll({
|
|
689
|
+
...this.extraOptions,
|
|
690
|
+
where: asWhere({
|
|
691
|
+
[this.relation.relatedKey]: relatedIds,
|
|
692
|
+
...this.extraWhere
|
|
693
|
+
})
|
|
694
|
+
});
|
|
695
|
+
return rows.map((row) => this.related.newFromRecord(row));
|
|
696
|
+
}
|
|
697
|
+
async first() {
|
|
698
|
+
const rows = await this.get();
|
|
699
|
+
return rows[0] ?? null;
|
|
700
|
+
}
|
|
701
|
+
async count() {
|
|
702
|
+
return (await this.get()).length;
|
|
703
|
+
}
|
|
704
|
+
async attach(ids) {
|
|
705
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
706
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
707
|
+
for (const id of list) {
|
|
708
|
+
await this.connection().unsafe(`INSERT INTO ${this.relation.pivotTable} (${String(this.relation.foreignPivotKey)}, ${String(this.relation.relatedPivotKey)}) VALUES ($1, $2)`, [parentId, id]);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
async detach(ids) {
|
|
712
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
713
|
+
if (ids === undefined) {
|
|
714
|
+
await this.connection().unsafe(`DELETE FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1`, [parentId]);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
718
|
+
await this.connection().unsafe(`DELETE FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1 AND ${String(this.relation.relatedPivotKey)} = ANY($2)`, [parentId, list]);
|
|
719
|
+
}
|
|
720
|
+
async sync(ids) {
|
|
721
|
+
await this.detach();
|
|
722
|
+
if (ids.length > 0) {
|
|
723
|
+
await this.attach(ids);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
async create(attributes = {}) {
|
|
727
|
+
const related = await this.related.create(attributes);
|
|
728
|
+
await this.attach(related.id);
|
|
729
|
+
return related;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
class MorphManyRelationQuery {
|
|
734
|
+
parent;
|
|
735
|
+
related;
|
|
736
|
+
relation;
|
|
737
|
+
kind = "morphMany";
|
|
738
|
+
extraWhere = {};
|
|
739
|
+
extraOptions = {};
|
|
740
|
+
constructor(parent, related, relation) {
|
|
741
|
+
this.parent = parent;
|
|
742
|
+
this.related = related;
|
|
743
|
+
this.relation = relation;
|
|
744
|
+
}
|
|
745
|
+
where(where) {
|
|
746
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
747
|
+
return this;
|
|
748
|
+
}
|
|
749
|
+
applyEagerLoad(query, alias) {
|
|
750
|
+
query.withMorphMany(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
751
|
+
}
|
|
752
|
+
hydrateEager(row, alias) {
|
|
753
|
+
const value = row[alias] ?? [];
|
|
754
|
+
const rows = Array.isArray(value) ? value : [];
|
|
755
|
+
return rows.map((item) => this.related.newFromRecord(item));
|
|
756
|
+
}
|
|
757
|
+
toExistsClause(parentTable) {
|
|
758
|
+
const childTable = this.related.repository().getTable().name;
|
|
759
|
+
const extra = buildAdvancedWhereClause(childTable, {
|
|
760
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
761
|
+
...this.extraWhere
|
|
762
|
+
}, [], []);
|
|
763
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
764
|
+
const sql = `SELECT 1 FROM ${quoteIdentifier(childTable)} WHERE ${qualifyColumn(childTable, this.relation.morphIdKey)} = ${qualifyColumn(parentTable, this.relation.localKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
|
|
765
|
+
return { sql, params: extra.params };
|
|
766
|
+
}
|
|
767
|
+
async get() {
|
|
768
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
769
|
+
const rows = await repository.query(asWhere({
|
|
770
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
771
|
+
[this.relation.morphIdKey]: this.parent.get(this.relation.localKey),
|
|
772
|
+
...this.extraWhere
|
|
773
|
+
})).get();
|
|
774
|
+
return rows.map((row) => this.related.newFromRecord(row));
|
|
775
|
+
}
|
|
776
|
+
async first() {
|
|
777
|
+
const rows = await this.get();
|
|
778
|
+
return rows[0] ?? null;
|
|
779
|
+
}
|
|
780
|
+
async create(attributes = {}) {
|
|
781
|
+
return this.related.create(attributes, {
|
|
782
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
783
|
+
[this.relation.morphIdKey]: this.parent.get(this.relation.localKey)
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
class MorphOneRelationQuery {
|
|
789
|
+
relation;
|
|
790
|
+
kind = "morphOne";
|
|
791
|
+
inner;
|
|
792
|
+
constructor(parent, related, relation) {
|
|
793
|
+
this.relation = relation;
|
|
794
|
+
this.inner = new MorphManyRelationQuery(parent, related, {
|
|
795
|
+
type: "morphMany",
|
|
796
|
+
name: relation.name,
|
|
797
|
+
localKey: relation.localKey,
|
|
798
|
+
morphTypeKey: relation.morphTypeKey,
|
|
799
|
+
morphIdKey: relation.morphIdKey,
|
|
800
|
+
morphType: relation.morphType
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
where(where) {
|
|
804
|
+
this.inner.where(where);
|
|
805
|
+
return this;
|
|
806
|
+
}
|
|
807
|
+
applyEagerLoad(query, alias) {
|
|
808
|
+
this.inner.applyEagerLoad(query, alias);
|
|
809
|
+
}
|
|
810
|
+
hydrateEager(row, alias) {
|
|
811
|
+
const hydrated = this.inner.hydrateEager(row, alias);
|
|
812
|
+
return hydrated[0];
|
|
813
|
+
}
|
|
814
|
+
toExistsClause(parentTable) {
|
|
815
|
+
return this.inner.toExistsClause(parentTable);
|
|
816
|
+
}
|
|
817
|
+
async get() {
|
|
818
|
+
return this.inner.first();
|
|
819
|
+
}
|
|
820
|
+
async create(attributes = {}) {
|
|
821
|
+
return this.inner.create(attributes);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
class MorphToRelationQuery {
|
|
826
|
+
parent;
|
|
827
|
+
relatedByType;
|
|
828
|
+
relation;
|
|
829
|
+
kind = "morphTo";
|
|
830
|
+
constructor(parent, relatedByType, relation) {
|
|
831
|
+
this.parent = parent;
|
|
832
|
+
this.relatedByType = relatedByType;
|
|
833
|
+
this.relation = relation;
|
|
834
|
+
}
|
|
835
|
+
applyEagerLoad(query, alias) {
|
|
836
|
+
const repositories = new Map(Object.entries(this.relatedByType).map(([type, model]) => [
|
|
837
|
+
type,
|
|
838
|
+
model.repository()
|
|
839
|
+
]));
|
|
840
|
+
query.withMorphTo(alias, this.relation, repositories);
|
|
841
|
+
}
|
|
842
|
+
hydrateEager(row, alias) {
|
|
843
|
+
return row[alias];
|
|
844
|
+
}
|
|
845
|
+
toExistsClause(parentTable) {
|
|
846
|
+
const type = String(this.parent.get(this.relation.morphTypeKey) ?? "");
|
|
847
|
+
const related = this.relatedByType[type];
|
|
848
|
+
if (!related) {
|
|
849
|
+
return { sql: "SELECT 1 WHERE 1 = 0", params: [] };
|
|
850
|
+
}
|
|
851
|
+
const relatedTable = related.repository().getTable();
|
|
852
|
+
return {
|
|
853
|
+
sql: `SELECT 1 FROM ${quoteIdentifier(relatedTable.name)} WHERE ${qualifyColumn(relatedTable.name, relatedTable.primaryKey)} = ${qualifyColumn(parentTable, this.relation.morphIdKey)}`,
|
|
854
|
+
params: []
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
async get() {
|
|
858
|
+
const type = String(this.parent.get(this.relation.morphTypeKey) ?? "");
|
|
859
|
+
const id = this.parent.get(this.relation.morphIdKey);
|
|
860
|
+
const related = this.relatedByType[type];
|
|
861
|
+
if (!related || id === null || id === undefined) {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
const table = related.repository().getTable();
|
|
865
|
+
const row = await related.repository().withConnection(this.parent.getRepository().getConnection()).query(asWhere({ [table.primaryKey]: id })).first();
|
|
866
|
+
return row ? related.newFromRecord(row) : null;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
419
870
|
// ../../src/core/database/relationships.ts
|
|
420
871
|
function hasMany(definition) {
|
|
421
872
|
return {
|
|
@@ -565,7 +1016,54 @@ function indexMorphToRelation(children, parentsByType, relation) {
|
|
|
565
1016
|
// ../../src/core/database/model.ts
|
|
566
1017
|
var modelRepositories = new WeakMap;
|
|
567
1018
|
var modelGlobalScopes = new WeakMap;
|
|
1019
|
+
var modelObservers = new WeakMap;
|
|
568
1020
|
var modelBooted = new WeakSet;
|
|
1021
|
+
async function runObservers(model, hook) {
|
|
1022
|
+
const observers = modelObservers.get(model.constructor) ?? [];
|
|
1023
|
+
for (const observer of observers) {
|
|
1024
|
+
const handler = observer[hook];
|
|
1025
|
+
if (handler && await handler(model) === false) {
|
|
1026
|
+
return false;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return true;
|
|
1030
|
+
}
|
|
1031
|
+
function accessorName(key) {
|
|
1032
|
+
const pascal = key.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
1033
|
+
return `get${pascal.charAt(0).toUpperCase()}${pascal.slice(1)}Attribute`;
|
|
1034
|
+
}
|
|
1035
|
+
function constrainRelationExists(model, name, constrain, not) {
|
|
1036
|
+
const statics = modelStatics(model);
|
|
1037
|
+
ensureBooted(model);
|
|
1038
|
+
const repository = resolveModelRepository(model);
|
|
1039
|
+
const dummy = statics.newFromRecord({});
|
|
1040
|
+
const method = dummy[name];
|
|
1041
|
+
if (typeof method !== "function") {
|
|
1042
|
+
throw new Error(`${model.name} has no relation method ${name}().`);
|
|
1043
|
+
}
|
|
1044
|
+
const relationQuery = method.call(dummy);
|
|
1045
|
+
constrain?.(relationQuery);
|
|
1046
|
+
const exists = relationQuery.toExistsClause(repository.getTable().name);
|
|
1047
|
+
const query = Model.query.call(model);
|
|
1048
|
+
return not ? query.whereNotExists(exists.sql, exists.params) : query.whereExists(exists.sql, exists.params);
|
|
1049
|
+
}
|
|
1050
|
+
async function loadNested(model, path) {
|
|
1051
|
+
const [head, ...rest] = path.split(".");
|
|
1052
|
+
if (!head) {
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
await model.load(head);
|
|
1056
|
+
if (rest.length === 0) {
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const loaded = model.loaded(head);
|
|
1060
|
+
const children = Array.isArray(loaded) ? loaded : loaded ? [loaded] : [];
|
|
1061
|
+
for (const child of children) {
|
|
1062
|
+
if (child && typeof child === "object" && typeof child.load === "function") {
|
|
1063
|
+
await loadNested(child, rest.join("."));
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
569
1067
|
function resolveModelRepository(model) {
|
|
570
1068
|
const repository = modelRepositories.get(model);
|
|
571
1069
|
if (!repository) {
|
|
@@ -680,12 +1178,22 @@ class Model {
|
|
|
680
1178
|
static $guarded;
|
|
681
1179
|
static $casts = {};
|
|
682
1180
|
static $timestamps = true;
|
|
1181
|
+
static $hidden;
|
|
1182
|
+
static $visible;
|
|
1183
|
+
static $appends;
|
|
683
1184
|
_exists;
|
|
1185
|
+
loadedRelations = {};
|
|
1186
|
+
hiddenOverrides = [];
|
|
1187
|
+
visibleOverrides = [];
|
|
1188
|
+
appended = [];
|
|
684
1189
|
constructor(attributes, repository, exists = true) {
|
|
685
1190
|
this.attributes = attributes;
|
|
686
1191
|
this.repository = repository;
|
|
687
1192
|
this._exists = exists;
|
|
688
1193
|
}
|
|
1194
|
+
getRepository() {
|
|
1195
|
+
return this.repository;
|
|
1196
|
+
}
|
|
689
1197
|
get $exists() {
|
|
690
1198
|
return this._exists;
|
|
691
1199
|
}
|
|
@@ -698,6 +1206,49 @@ class Model {
|
|
|
698
1206
|
toObject() {
|
|
699
1207
|
return { ...this.attributes };
|
|
700
1208
|
}
|
|
1209
|
+
toArray() {
|
|
1210
|
+
const ModelClass = modelStatics(this.constructor);
|
|
1211
|
+
const hidden = new Set([...ModelClass.$hidden ?? [], ...this.hiddenOverrides]);
|
|
1212
|
+
const visible = this.visibleOverrides.length > 0 ? this.visibleOverrides : ModelClass.$visible;
|
|
1213
|
+
const data = { ...this.attributes };
|
|
1214
|
+
if (visible && visible.length > 0) {
|
|
1215
|
+
for (const key of Object.keys(data)) {
|
|
1216
|
+
if (!visible.includes(key)) {
|
|
1217
|
+
delete data[key];
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
for (const key of hidden) {
|
|
1222
|
+
delete data[key];
|
|
1223
|
+
}
|
|
1224
|
+
for (const key of [...ModelClass.$appends ?? [], ...this.appended]) {
|
|
1225
|
+
const accessor = this[accessorName(key)];
|
|
1226
|
+
if (typeof accessor === "function") {
|
|
1227
|
+
data[key] = accessor.call(this);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
for (const [name, value] of Object.entries(this.loadedRelations)) {
|
|
1231
|
+
if (!hidden.has(name) && (!visible || visible.includes(name) || this.appended.includes(name))) {
|
|
1232
|
+
data[name] = value;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return data;
|
|
1236
|
+
}
|
|
1237
|
+
toJSON() {
|
|
1238
|
+
return this.toArray();
|
|
1239
|
+
}
|
|
1240
|
+
makeHidden(...keys) {
|
|
1241
|
+
this.hiddenOverrides.push(...keys);
|
|
1242
|
+
return this;
|
|
1243
|
+
}
|
|
1244
|
+
makeVisible(...keys) {
|
|
1245
|
+
this.visibleOverrides.push(...keys);
|
|
1246
|
+
return this;
|
|
1247
|
+
}
|
|
1248
|
+
append(...keys) {
|
|
1249
|
+
this.appended.push(...keys);
|
|
1250
|
+
return this;
|
|
1251
|
+
}
|
|
701
1252
|
primaryKey() {
|
|
702
1253
|
throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
|
|
703
1254
|
}
|
|
@@ -718,6 +1269,10 @@ class Model {
|
|
|
718
1269
|
return new statics(hydrated, repository, exists);
|
|
719
1270
|
}
|
|
720
1271
|
static boot() {}
|
|
1272
|
+
static observe(observer) {
|
|
1273
|
+
const existing = modelObservers.get(this) ?? [];
|
|
1274
|
+
modelObservers.set(this, [...existing, observer]);
|
|
1275
|
+
}
|
|
721
1276
|
static addGlobalScope(_name, scope) {
|
|
722
1277
|
ensureBooted(this);
|
|
723
1278
|
const existing = modelGlobalScopes.get(this) ?? [];
|
|
@@ -738,17 +1293,89 @@ class Model {
|
|
|
738
1293
|
}
|
|
739
1294
|
return query;
|
|
740
1295
|
}
|
|
741
|
-
static
|
|
1296
|
+
static newFromRecord(record, exists = true) {
|
|
1297
|
+
const repository = resolveModelRepository(this);
|
|
1298
|
+
return modelStatics(this).fromRecord(record, repository, exists);
|
|
1299
|
+
}
|
|
1300
|
+
static async create(attributes, forced = {}) {
|
|
742
1301
|
const statics = modelStatics(this);
|
|
743
1302
|
ensureBooted(this);
|
|
744
1303
|
const repository = resolveModelRepository(this);
|
|
745
1304
|
const table = repository.getTable();
|
|
746
1305
|
const timestamps = statics.$timestamps ?? true;
|
|
747
|
-
const assignable =
|
|
1306
|
+
const assignable = {
|
|
1307
|
+
...filterMassAssignable(statics.$fillable, statics.$guarded, attributes),
|
|
1308
|
+
...forced
|
|
1309
|
+
};
|
|
748
1310
|
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
749
1311
|
const payload = statics.dehydrateAttributes(withTimestamps);
|
|
1312
|
+
const pending = statics.newFromRecord({ ...payload }, false);
|
|
1313
|
+
if (await runObservers(pending, "creating") === false) {
|
|
1314
|
+
throw new Error(`${this.name}.create() was cancelled by an observer.`);
|
|
1315
|
+
}
|
|
750
1316
|
const record = await repository.create(payload);
|
|
751
|
-
|
|
1317
|
+
const created = statics.fromRecord(record, repository, true);
|
|
1318
|
+
await runObservers(created, "created");
|
|
1319
|
+
return created;
|
|
1320
|
+
}
|
|
1321
|
+
static with(...relations) {
|
|
1322
|
+
const statics = modelStatics(this);
|
|
1323
|
+
ensureBooted(this);
|
|
1324
|
+
const repository = resolveModelRepository(this);
|
|
1325
|
+
const dummy = statics.fromRecord({}, repository, false);
|
|
1326
|
+
const resolved = relations.map((path) => {
|
|
1327
|
+
const name = path.split(".")[0] ?? path;
|
|
1328
|
+
const method = dummy[name];
|
|
1329
|
+
if (typeof method !== "function") {
|
|
1330
|
+
throw new Error(`${this.name} has no relation method ${name}().`);
|
|
1331
|
+
}
|
|
1332
|
+
const relationQuery = method.call(dummy);
|
|
1333
|
+
return { name, path, relationQuery };
|
|
1334
|
+
});
|
|
1335
|
+
const query = Model.query.call(this);
|
|
1336
|
+
for (const { name, relationQuery } of resolved) {
|
|
1337
|
+
if (relationQuery.kind !== "belongsToMany") {
|
|
1338
|
+
relationQuery.applyEagerLoad(query, name);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
return {
|
|
1342
|
+
async get() {
|
|
1343
|
+
const rows = await query.get();
|
|
1344
|
+
const models = [];
|
|
1345
|
+
for (const row of rows) {
|
|
1346
|
+
const model = statics.fromRecord(row, repository, true);
|
|
1347
|
+
for (const { name, path, relationQuery } of resolved) {
|
|
1348
|
+
if (relationQuery.kind === "belongsToMany") {
|
|
1349
|
+
await model.load(path.includes(".") ? path : name);
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
1352
|
+
model.setLoaded(name, relationQuery.hydrateEager(row, name));
|
|
1353
|
+
const nested = path.split(".").slice(1).join(".");
|
|
1354
|
+
if (nested) {
|
|
1355
|
+
await loadNested(model, path);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
models.push(model);
|
|
1359
|
+
}
|
|
1360
|
+
return models;
|
|
1361
|
+
},
|
|
1362
|
+
async first() {
|
|
1363
|
+
const [model] = await this.get();
|
|
1364
|
+
return model ?? null;
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
static whereHas(name, constrain) {
|
|
1369
|
+
return constrainRelationExists(this, name, constrain, false);
|
|
1370
|
+
}
|
|
1371
|
+
static has(name) {
|
|
1372
|
+
return constrainRelationExists(this, name, undefined, false);
|
|
1373
|
+
}
|
|
1374
|
+
static doesntHave(name) {
|
|
1375
|
+
return constrainRelationExists(this, name, undefined, true);
|
|
1376
|
+
}
|
|
1377
|
+
static whereDoesntHave(name, constrain) {
|
|
1378
|
+
return constrainRelationExists(this, name, constrain, true);
|
|
752
1379
|
}
|
|
753
1380
|
static async find(id) {
|
|
754
1381
|
const statics = modelStatics(this);
|
|
@@ -777,6 +1404,9 @@ class Model {
|
|
|
777
1404
|
const rows = await query.get();
|
|
778
1405
|
return rows.map((row) => statics.fromRecord(row, repository, true));
|
|
779
1406
|
}
|
|
1407
|
+
static where(where) {
|
|
1408
|
+
return Model.query.call(this).where(where);
|
|
1409
|
+
}
|
|
780
1410
|
static async firstWhere(where, options = {}) {
|
|
781
1411
|
const statics = modelStatics(this);
|
|
782
1412
|
const repository = resolveModelRepository(this);
|
|
@@ -787,15 +1417,41 @@ class Model {
|
|
|
787
1417
|
const record = await query.first();
|
|
788
1418
|
return record ? statics.fromRecord(record, repository, true) : null;
|
|
789
1419
|
}
|
|
1420
|
+
static async firstOrNew(where, values = {}) {
|
|
1421
|
+
const existing = await Model.firstWhere.call(this, where);
|
|
1422
|
+
if (existing) {
|
|
1423
|
+
return existing;
|
|
1424
|
+
}
|
|
1425
|
+
return modelStatics(this).newFromRecord({ ...where, ...values }, false);
|
|
1426
|
+
}
|
|
1427
|
+
static async firstOrCreate(where, values = {}) {
|
|
1428
|
+
const existing = await Model.firstWhere.call(this, where);
|
|
1429
|
+
if (existing) {
|
|
1430
|
+
return existing;
|
|
1431
|
+
}
|
|
1432
|
+
return Model.create.call(this, { ...where, ...values });
|
|
1433
|
+
}
|
|
1434
|
+
static async updateOrCreate(where, values = {}) {
|
|
1435
|
+
const existing = await Model.firstWhere.call(this, where);
|
|
1436
|
+
if (existing) {
|
|
1437
|
+
return existing.update(values);
|
|
1438
|
+
}
|
|
1439
|
+
return Model.create.call(this, { ...where, ...values });
|
|
1440
|
+
}
|
|
790
1441
|
async save() {
|
|
791
1442
|
const ModelClass = modelStatics(this.constructor);
|
|
792
1443
|
const timestamps = ModelClass.$timestamps ?? true;
|
|
793
1444
|
const casts = ModelClass.$casts ?? {};
|
|
794
1445
|
const table = this.repository.getTable();
|
|
795
|
-
|
|
1446
|
+
const updating = this.$exists;
|
|
1447
|
+
if (await runObservers(this, updating ? "updating" : "creating") === false) {
|
|
1448
|
+
return this;
|
|
1449
|
+
}
|
|
1450
|
+
if (updating) {
|
|
796
1451
|
const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
|
|
797
1452
|
const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
|
|
798
1453
|
this.attributes = ModelClass.hydrateAttributes(record2);
|
|
1454
|
+
await runObservers(this, "updated");
|
|
799
1455
|
return this;
|
|
800
1456
|
}
|
|
801
1457
|
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
|
|
@@ -804,6 +1460,7 @@ class Model {
|
|
|
804
1460
|
const record = await this.repository.create(payload);
|
|
805
1461
|
this.attributes = ModelClass.hydrateAttributes(record);
|
|
806
1462
|
this._exists = true;
|
|
1463
|
+
await runObservers(this, "created");
|
|
807
1464
|
return this;
|
|
808
1465
|
}
|
|
809
1466
|
async update(changes) {
|
|
@@ -813,10 +1470,14 @@ class Model {
|
|
|
813
1470
|
return await this.save();
|
|
814
1471
|
}
|
|
815
1472
|
async delete() {
|
|
816
|
-
if (
|
|
817
|
-
return
|
|
1473
|
+
if (await runObservers(this, "deleting") === false) {
|
|
1474
|
+
return false;
|
|
818
1475
|
}
|
|
819
|
-
|
|
1476
|
+
const deleted = resolveSoftDeleteColumn(this.repository.getTable()) ? await this.repository.deleteById(this.id) : await this.repository.forceDeleteById(this.id);
|
|
1477
|
+
if (deleted) {
|
|
1478
|
+
await runObservers(this, "deleted");
|
|
1479
|
+
}
|
|
1480
|
+
return deleted;
|
|
820
1481
|
}
|
|
821
1482
|
async forceDelete() {
|
|
822
1483
|
return await this.repository.forceDeleteById(this.id);
|
|
@@ -865,6 +1526,91 @@ class Model {
|
|
|
865
1526
|
const loaded = grouped.get(parentId) ?? [];
|
|
866
1527
|
return Object.assign(this, { [as]: loaded });
|
|
867
1528
|
}
|
|
1529
|
+
hasMany(related, foreignKey, localKey) {
|
|
1530
|
+
const table = this.repository.getTable();
|
|
1531
|
+
return new HasManyRelationQuery(this, related, hasMany({
|
|
1532
|
+
name: related.repository().getTable().name,
|
|
1533
|
+
localKey: localKey ?? table.primaryKey,
|
|
1534
|
+
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
1535
|
+
}));
|
|
1536
|
+
}
|
|
1537
|
+
hasOne(related, foreignKey, localKey) {
|
|
1538
|
+
const table = this.repository.getTable();
|
|
1539
|
+
return new HasOneRelationQuery(this, related, hasOne({
|
|
1540
|
+
name: related.repository().getTable().name,
|
|
1541
|
+
localKey: localKey ?? table.primaryKey,
|
|
1542
|
+
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
1543
|
+
}));
|
|
1544
|
+
}
|
|
1545
|
+
belongsTo(related, foreignKey, ownerKey) {
|
|
1546
|
+
const relatedTable = related.repository().getTable();
|
|
1547
|
+
return new BelongsToRelationQuery(this, related, belongsTo({
|
|
1548
|
+
name: relatedTable.name,
|
|
1549
|
+
foreignKey: foreignKey ?? foreignKeyFromTable(relatedTable.name),
|
|
1550
|
+
ownerKey: ownerKey ?? relatedTable.primaryKey
|
|
1551
|
+
}));
|
|
1552
|
+
}
|
|
1553
|
+
belongsToMany(related, pivotTable, foreignPivotKey, relatedPivotKey) {
|
|
1554
|
+
const table = this.repository.getTable();
|
|
1555
|
+
const relatedTable = related.repository().getTable();
|
|
1556
|
+
return new BelongsToManyRelationQuery(this, related, belongsToMany({
|
|
1557
|
+
name: relatedTable.name,
|
|
1558
|
+
pivotTable: pivotTable ?? pivotTableName(table.name, relatedTable.name),
|
|
1559
|
+
parentKey: table.primaryKey,
|
|
1560
|
+
relatedKey: relatedTable.primaryKey,
|
|
1561
|
+
foreignPivotKey: foreignPivotKey ?? foreignKeyFromTable(table.name),
|
|
1562
|
+
relatedPivotKey: relatedPivotKey ?? foreignKeyFromTable(relatedTable.name)
|
|
1563
|
+
}));
|
|
1564
|
+
}
|
|
1565
|
+
morphMany(related, morphName, typeKey, idKey) {
|
|
1566
|
+
const table = this.repository.getTable();
|
|
1567
|
+
return new MorphManyRelationQuery(this, related, morphMany({
|
|
1568
|
+
name: morphName,
|
|
1569
|
+
localKey: table.primaryKey,
|
|
1570
|
+
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
1571
|
+
morphIdKey: idKey ?? `${morphName}_id`,
|
|
1572
|
+
morphType: table.name
|
|
1573
|
+
}));
|
|
1574
|
+
}
|
|
1575
|
+
morphOne(related, morphName, typeKey, idKey) {
|
|
1576
|
+
const table = this.repository.getTable();
|
|
1577
|
+
return new MorphOneRelationQuery(this, related, morphOne({
|
|
1578
|
+
name: morphName,
|
|
1579
|
+
localKey: table.primaryKey,
|
|
1580
|
+
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
1581
|
+
morphIdKey: idKey ?? `${morphName}_id`,
|
|
1582
|
+
morphType: table.name
|
|
1583
|
+
}));
|
|
1584
|
+
}
|
|
1585
|
+
morphTo(relatedByType, morphName = "imageable", typeKey, idKey) {
|
|
1586
|
+
return new MorphToRelationQuery(this, relatedByType, morphTo({
|
|
1587
|
+
name: morphName,
|
|
1588
|
+
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
1589
|
+
morphIdKey: idKey ?? `${morphName}_id`
|
|
1590
|
+
}));
|
|
1591
|
+
}
|
|
1592
|
+
async load(...names) {
|
|
1593
|
+
for (const name of names) {
|
|
1594
|
+
if (name.includes(".")) {
|
|
1595
|
+
await loadNested(this, name);
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
const method = this[name];
|
|
1599
|
+
if (typeof method !== "function") {
|
|
1600
|
+
throw new Error(`${this.constructor.name} has no relation method ${name}().`);
|
|
1601
|
+
}
|
|
1602
|
+
const relationQuery = method.call(this);
|
|
1603
|
+
this.loadedRelations[name] = await relationQuery.get();
|
|
1604
|
+
}
|
|
1605
|
+
return this;
|
|
1606
|
+
}
|
|
1607
|
+
loaded(name) {
|
|
1608
|
+
return this.loadedRelations[name];
|
|
1609
|
+
}
|
|
1610
|
+
setLoaded(name, value) {
|
|
1611
|
+
this.loadedRelations[name] = value;
|
|
1612
|
+
return this;
|
|
1613
|
+
}
|
|
868
1614
|
mergeAttributes(patch) {
|
|
869
1615
|
Object.assign(this.attributes, patch);
|
|
870
1616
|
return this;
|
|
@@ -876,7 +1622,14 @@ function registerModelRepository(model, repository) {
|
|
|
876
1622
|
return model;
|
|
877
1623
|
}
|
|
878
1624
|
export {
|
|
1625
|
+
BelongsToManyRelationQuery,
|
|
1626
|
+
BelongsToRelationQuery,
|
|
1627
|
+
HasManyRelationQuery,
|
|
1628
|
+
HasOneRelationQuery,
|
|
879
1629
|
Model,
|
|
1630
|
+
MorphManyRelationQuery,
|
|
1631
|
+
MorphOneRelationQuery,
|
|
1632
|
+
MorphToRelationQuery,
|
|
880
1633
|
applyCasts,
|
|
881
1634
|
dehydrateValue,
|
|
882
1635
|
filterMassAssignable,
|