@getstrata/core 0.5.98 → 0.5.100
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 +30 -0
- package/dist/core/database/baseRepository.d.ts +3 -1
- package/dist/core/database/factory.d.ts +19 -4
- package/dist/core/database/index.d.ts +1 -1
- package/dist/core/database/model.d.ts +56 -19
- package/dist/core/database/relationQuery.d.ts +24 -1
- package/dist/core/database/relationships.d.ts +4 -2
- package/dist/core/database/repositoryQuery.d.ts +4 -1
- package/dist/core/http/resources.d.ts +2 -1
- package/dist/entries/database/factory.js +44 -2
- package/dist/entries/database/model.js +497 -145
- package/dist/entries/database/query.js +1 -1
- package/dist/entries/database/relationships.js +85 -21
- package/dist/entries/database/repositoryQuery.js +242 -8
- package/dist/entries/database/schema.js +1 -1
- package/dist/entries/http/resources.js +9 -3
- package/dist/framework/public-api.d.ts +1 -1
- package/dist/index.js +606 -178
- package/package.json +1 -1
|
@@ -97,7 +97,7 @@ function buildOperatorClauses(column, operator, params) {
|
|
|
97
97
|
clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
|
|
98
98
|
}
|
|
99
99
|
if (operator.ilike !== undefined) {
|
|
100
|
-
clauses.push(`${column} ILIKE ${pushParam(params,
|
|
100
|
+
clauses.push(`${column} ILIKE ${pushParam(params, operator.ilike)}`);
|
|
101
101
|
}
|
|
102
102
|
if (operator.tsMatch !== undefined) {
|
|
103
103
|
clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
|
|
@@ -458,6 +458,9 @@ function ownerId(owner, ownerKey) {
|
|
|
458
458
|
}
|
|
459
459
|
throw new Error("belongsTo.associate() requires a related model or { id }.");
|
|
460
460
|
}
|
|
461
|
+
function thenGet(get, onfulfilled, onrejected) {
|
|
462
|
+
return get().then(onfulfilled ?? undefined, onrejected ?? undefined);
|
|
463
|
+
}
|
|
461
464
|
|
|
462
465
|
class HasManyRelationQuery {
|
|
463
466
|
parent;
|
|
@@ -521,13 +524,28 @@ class HasManyRelationQuery {
|
|
|
521
524
|
return rows[0] ?? null;
|
|
522
525
|
}
|
|
523
526
|
async count() {
|
|
524
|
-
return
|
|
527
|
+
return this.scopedQuery().count();
|
|
528
|
+
}
|
|
529
|
+
then(onfulfilled, onrejected) {
|
|
530
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
525
531
|
}
|
|
526
532
|
async create(attributes = {}) {
|
|
527
533
|
return this.related.create(attributes, {
|
|
528
534
|
[this.relation.foreignKey]: this.parent.get(this.relation.localKey)
|
|
529
535
|
});
|
|
530
536
|
}
|
|
537
|
+
async save(related) {
|
|
538
|
+
const forced = {
|
|
539
|
+
[this.relation.foreignKey]: this.parent.get(this.relation.localKey)
|
|
540
|
+
};
|
|
541
|
+
const savable = related;
|
|
542
|
+
if (typeof savable.save === "function") {
|
|
543
|
+
savable.mergeAttributes?.(forced);
|
|
544
|
+
await savable.save();
|
|
545
|
+
return related;
|
|
546
|
+
}
|
|
547
|
+
return this.create(related);
|
|
548
|
+
}
|
|
531
549
|
async createMany(records) {
|
|
532
550
|
const created = [];
|
|
533
551
|
for (const attributes of records) {
|
|
@@ -575,11 +593,17 @@ class HasOneRelationQuery {
|
|
|
575
593
|
return this.get();
|
|
576
594
|
}
|
|
577
595
|
async count() {
|
|
578
|
-
return
|
|
596
|
+
return this.inner.count();
|
|
597
|
+
}
|
|
598
|
+
then(onfulfilled, onrejected) {
|
|
599
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
579
600
|
}
|
|
580
601
|
async create(attributes = {}) {
|
|
581
602
|
return this.inner.create(attributes);
|
|
582
603
|
}
|
|
604
|
+
async save(related) {
|
|
605
|
+
return this.inner.save(related);
|
|
606
|
+
}
|
|
583
607
|
}
|
|
584
608
|
|
|
585
609
|
class BelongsToRelationQuery {
|
|
@@ -587,12 +611,17 @@ class BelongsToRelationQuery {
|
|
|
587
611
|
related;
|
|
588
612
|
relation;
|
|
589
613
|
kind = "belongsTo";
|
|
614
|
+
extraWhere = {};
|
|
590
615
|
extraOptions = {};
|
|
591
616
|
constructor(parent, related, relation) {
|
|
592
617
|
this.parent = parent;
|
|
593
618
|
this.related = related;
|
|
594
619
|
this.relation = relation;
|
|
595
620
|
}
|
|
621
|
+
where(where) {
|
|
622
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
623
|
+
return this;
|
|
624
|
+
}
|
|
596
625
|
orderBy(orderBy) {
|
|
597
626
|
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
598
627
|
return this;
|
|
@@ -606,10 +635,10 @@ class BelongsToRelationQuery {
|
|
|
606
635
|
}
|
|
607
636
|
toExistsClause(parentTable) {
|
|
608
637
|
const relatedTable = this.related.repository().getTable().name;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
};
|
|
638
|
+
const extra = buildAdvancedWhereClause(relatedTable, this.extraWhere, [], []);
|
|
639
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
640
|
+
const sql = `SELECT 1 FROM ${quoteIdentifier(relatedTable)} WHERE ${qualifyColumn(relatedTable, this.relation.ownerKey)} = ${qualifyColumn(parentTable, this.relation.foreignKey)}${extraSql ? ` AND ${extraSql}` : ""}`;
|
|
641
|
+
return { sql, params: extra.params };
|
|
613
642
|
}
|
|
614
643
|
async get() {
|
|
615
644
|
const foreign = this.parent.get(this.relation.foreignKey);
|
|
@@ -617,7 +646,7 @@ class BelongsToRelationQuery {
|
|
|
617
646
|
return null;
|
|
618
647
|
}
|
|
619
648
|
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
620
|
-
let query = repository.query(asWhere({ [this.relation.ownerKey]: foreign }));
|
|
649
|
+
let query = repository.query(asWhere({ [this.relation.ownerKey]: foreign, ...this.extraWhere }));
|
|
621
650
|
if (this.extraOptions.orderBy) {
|
|
622
651
|
query = query.orderBy(this.extraOptions.orderBy);
|
|
623
652
|
}
|
|
@@ -627,6 +656,9 @@ class BelongsToRelationQuery {
|
|
|
627
656
|
async first() {
|
|
628
657
|
return this.get();
|
|
629
658
|
}
|
|
659
|
+
then(onfulfilled, onrejected) {
|
|
660
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
661
|
+
}
|
|
630
662
|
async associate(owner) {
|
|
631
663
|
await this.parent.getRepository().updateById(this.parent.id, {
|
|
632
664
|
[this.relation.foreignKey]: ownerId(owner, this.relation.ownerKey)
|
|
@@ -646,6 +678,7 @@ class BelongsToManyRelationQuery {
|
|
|
646
678
|
kind = "belongsToMany";
|
|
647
679
|
extraWhere = {};
|
|
648
680
|
extraOptions = {};
|
|
681
|
+
pivotValues = {};
|
|
649
682
|
constructor(parent, related, relation) {
|
|
650
683
|
this.parent = parent;
|
|
651
684
|
this.related = related;
|
|
@@ -659,7 +692,13 @@ class BelongsToManyRelationQuery {
|
|
|
659
692
|
this.extraOptions = { ...this.extraOptions, orderBy };
|
|
660
693
|
return this;
|
|
661
694
|
}
|
|
662
|
-
applyEagerLoad(
|
|
695
|
+
applyEagerLoad(query, alias) {
|
|
696
|
+
query.withBelongsToMany(alias, this.relation, this.related.repository(), this.extraOptions);
|
|
697
|
+
}
|
|
698
|
+
withPivotValues(values) {
|
|
699
|
+
this.pivotValues = { ...this.pivotValues, ...values };
|
|
700
|
+
return this;
|
|
701
|
+
}
|
|
663
702
|
hydrateEager(row, alias) {
|
|
664
703
|
const value = row[alias] ?? [];
|
|
665
704
|
const rows = Array.isArray(value) ? value : [];
|
|
@@ -699,13 +738,34 @@ class BelongsToManyRelationQuery {
|
|
|
699
738
|
return rows[0] ?? null;
|
|
700
739
|
}
|
|
701
740
|
async count() {
|
|
702
|
-
|
|
741
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
742
|
+
const rows = await this.connection().unsafe(`SELECT COUNT(*) AS count FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1`, [parentId]);
|
|
743
|
+
return Number(rows[0]?.count ?? 0);
|
|
744
|
+
}
|
|
745
|
+
then(onfulfilled, onrejected) {
|
|
746
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
703
747
|
}
|
|
704
748
|
async attach(ids) {
|
|
705
749
|
const list = Array.isArray(ids) ? ids : [ids];
|
|
706
750
|
const parentId = this.parent.get(this.relation.parentKey);
|
|
751
|
+
const extraKeys = Object.keys(this.pivotValues);
|
|
752
|
+
const extraColumns = extraKeys.length > 0 ? `, ${extraKeys.join(", ")}` : "";
|
|
753
|
+
const extraPlaceholders = extraKeys.map((_, index) => `$${index + 3}`).join(", ");
|
|
754
|
+
const extraValues = extraKeys.map((key) => this.pivotValues[key]);
|
|
707
755
|
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]);
|
|
756
|
+
await this.connection().unsafe(extraKeys.length > 0 ? `INSERT INTO ${this.relation.pivotTable} (${String(this.relation.foreignPivotKey)}, ${String(this.relation.relatedPivotKey)}${extraColumns}) VALUES ($1, $2, ${extraPlaceholders})` : `INSERT INTO ${this.relation.pivotTable} (${String(this.relation.foreignPivotKey)}, ${String(this.relation.relatedPivotKey)}) VALUES ($1, $2)`, [parentId, id, ...extraValues]);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
async toggle(ids) {
|
|
760
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
761
|
+
const parentId = this.parent.get(this.relation.parentKey);
|
|
762
|
+
for (const id of list) {
|
|
763
|
+
const existing = await this.connection().unsafe(`SELECT 1 FROM ${this.relation.pivotTable} WHERE ${String(this.relation.foreignPivotKey)} = $1 AND ${String(this.relation.relatedPivotKey)} = $2 LIMIT 1`, [parentId, id]);
|
|
764
|
+
if (existing.length > 0) {
|
|
765
|
+
await this.detach(id);
|
|
766
|
+
} else {
|
|
767
|
+
await this.attach(id);
|
|
768
|
+
}
|
|
709
769
|
}
|
|
710
770
|
}
|
|
711
771
|
async detach(ids) {
|
|
@@ -777,6 +837,17 @@ class MorphManyRelationQuery {
|
|
|
777
837
|
const rows = await this.get();
|
|
778
838
|
return rows[0] ?? null;
|
|
779
839
|
}
|
|
840
|
+
async count() {
|
|
841
|
+
const repository = this.related.repository().withConnection(this.parent.getRepository().getConnection());
|
|
842
|
+
return repository.query(asWhere({
|
|
843
|
+
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
844
|
+
[this.relation.morphIdKey]: this.parent.get(this.relation.localKey),
|
|
845
|
+
...this.extraWhere
|
|
846
|
+
})).count();
|
|
847
|
+
}
|
|
848
|
+
then(onfulfilled, onrejected) {
|
|
849
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
850
|
+
}
|
|
780
851
|
async create(attributes = {}) {
|
|
781
852
|
return this.related.create(attributes, {
|
|
782
853
|
[this.relation.morphTypeKey]: this.relation.morphType,
|
|
@@ -817,6 +888,15 @@ class MorphOneRelationQuery {
|
|
|
817
888
|
async get() {
|
|
818
889
|
return this.inner.first();
|
|
819
890
|
}
|
|
891
|
+
async first() {
|
|
892
|
+
return this.get();
|
|
893
|
+
}
|
|
894
|
+
async count() {
|
|
895
|
+
return this.inner.count();
|
|
896
|
+
}
|
|
897
|
+
then(onfulfilled, onrejected) {
|
|
898
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
899
|
+
}
|
|
820
900
|
async create(attributes = {}) {
|
|
821
901
|
return this.inner.create(attributes);
|
|
822
902
|
}
|
|
@@ -827,11 +907,16 @@ class MorphToRelationQuery {
|
|
|
827
907
|
relatedByType;
|
|
828
908
|
relation;
|
|
829
909
|
kind = "morphTo";
|
|
910
|
+
extraWhere = {};
|
|
830
911
|
constructor(parent, relatedByType, relation) {
|
|
831
912
|
this.parent = parent;
|
|
832
913
|
this.relatedByType = relatedByType;
|
|
833
914
|
this.relation = relation;
|
|
834
915
|
}
|
|
916
|
+
where(where) {
|
|
917
|
+
this.extraWhere = { ...this.extraWhere, ...where };
|
|
918
|
+
return this;
|
|
919
|
+
}
|
|
835
920
|
applyEagerLoad(query, alias) {
|
|
836
921
|
const repositories = new Map(Object.entries(this.relatedByType).map(([type, model]) => [
|
|
837
922
|
type,
|
|
@@ -849,9 +934,11 @@ class MorphToRelationQuery {
|
|
|
849
934
|
return { sql: "SELECT 1 WHERE 1 = 0", params: [] };
|
|
850
935
|
}
|
|
851
936
|
const relatedTable = related.repository().getTable();
|
|
937
|
+
const extra = buildAdvancedWhereClause(relatedTable.name, this.extraWhere, [], []);
|
|
938
|
+
const extraSql = extra.clause.replace(/^ WHERE /, "");
|
|
852
939
|
return {
|
|
853
|
-
sql: `SELECT 1 FROM ${quoteIdentifier(relatedTable.name)} WHERE ${qualifyColumn(relatedTable.name, relatedTable.primaryKey)} = ${qualifyColumn(parentTable, this.relation.morphIdKey)}`,
|
|
854
|
-
params:
|
|
940
|
+
sql: `SELECT 1 FROM ${quoteIdentifier(relatedTable.name)} WHERE ${qualifyColumn(relatedTable.name, relatedTable.primaryKey)} = ${qualifyColumn(parentTable, this.relation.morphIdKey)}${extraSql ? ` AND ${extraSql}` : ""}`,
|
|
941
|
+
params: extra.params
|
|
855
942
|
};
|
|
856
943
|
}
|
|
857
944
|
async get() {
|
|
@@ -862,9 +949,12 @@ class MorphToRelationQuery {
|
|
|
862
949
|
return null;
|
|
863
950
|
}
|
|
864
951
|
const table = related.repository().getTable();
|
|
865
|
-
const row = await related.repository().withConnection(this.parent.getRepository().getConnection()).query(asWhere({ [table.primaryKey]: id })).first();
|
|
952
|
+
const row = await related.repository().withConnection(this.parent.getRepository().getConnection()).query(asWhere({ [table.primaryKey]: id, ...this.extraWhere })).first();
|
|
866
953
|
return row ? related.newFromRecord(row) : null;
|
|
867
954
|
}
|
|
955
|
+
then(onfulfilled, onrejected) {
|
|
956
|
+
return thenGet(() => this.get(), onfulfilled, onrejected);
|
|
957
|
+
}
|
|
868
958
|
}
|
|
869
959
|
|
|
870
960
|
// ../../src/core/database/relationships.ts
|
|
@@ -892,26 +982,67 @@ function belongsToMany(definition) {
|
|
|
892
982
|
...definition
|
|
893
983
|
};
|
|
894
984
|
}
|
|
985
|
+
function relationMatchKey(value) {
|
|
986
|
+
if (value === null || value === undefined) {
|
|
987
|
+
return "";
|
|
988
|
+
}
|
|
989
|
+
if (typeof value === "bigint") {
|
|
990
|
+
return value.toString();
|
|
991
|
+
}
|
|
992
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
993
|
+
return String(value);
|
|
994
|
+
}
|
|
995
|
+
if (typeof value === "string" && /^-?\d+$/.test(value)) {
|
|
996
|
+
return BigInt(value).toString();
|
|
997
|
+
}
|
|
998
|
+
return String(value);
|
|
999
|
+
}
|
|
1000
|
+
function getByRelationKey(map, key) {
|
|
1001
|
+
if (map.has(key)) {
|
|
1002
|
+
return map.get(key);
|
|
1003
|
+
}
|
|
1004
|
+
const want = relationMatchKey(key);
|
|
1005
|
+
if (want === "") {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
for (const [existing, value] of map) {
|
|
1009
|
+
if (relationMatchKey(existing) === want) {
|
|
1010
|
+
return value;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
895
1015
|
function indexHasManyRelation(parents, children, relation) {
|
|
896
1016
|
const groups = new Map;
|
|
1017
|
+
const originalKeys = new Map;
|
|
897
1018
|
for (const parent of parents) {
|
|
898
|
-
|
|
1019
|
+
const key = relationMatchKey(parent[relation.localKey]);
|
|
1020
|
+
if (!groups.has(key)) {
|
|
1021
|
+
groups.set(key, []);
|
|
1022
|
+
originalKeys.set(key, parent[relation.localKey]);
|
|
1023
|
+
}
|
|
899
1024
|
}
|
|
900
1025
|
for (const child of children) {
|
|
901
|
-
const
|
|
902
|
-
const group = groups.get(key);
|
|
1026
|
+
const group = groups.get(relationMatchKey(child[relation.foreignKey]));
|
|
903
1027
|
if (!group) {
|
|
904
1028
|
continue;
|
|
905
1029
|
}
|
|
906
1030
|
group.push(child);
|
|
907
1031
|
}
|
|
908
|
-
|
|
1032
|
+
const result = new Map;
|
|
1033
|
+
for (const [key, group] of groups) {
|
|
1034
|
+
const original = originalKeys.get(key);
|
|
1035
|
+
if (original !== undefined) {
|
|
1036
|
+
result.set(original, group);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return result;
|
|
909
1040
|
}
|
|
910
1041
|
function indexHasOneRelation(parents, children, relation) {
|
|
911
1042
|
const grouped = indexHasManyRelation(parents, children, relation);
|
|
912
1043
|
const result = new Map;
|
|
913
1044
|
for (const parent of parents) {
|
|
914
|
-
const matches = grouped
|
|
1045
|
+
const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
|
|
915
1046
|
result.set(parent[relation.localKey], matches[0]);
|
|
916
1047
|
}
|
|
917
1048
|
return result;
|
|
@@ -919,12 +1050,12 @@ function indexHasOneRelation(parents, children, relation) {
|
|
|
919
1050
|
function indexBelongsToRelation(children, parents, relation) {
|
|
920
1051
|
const parentsById = new Map;
|
|
921
1052
|
for (const parent of parents) {
|
|
922
|
-
parentsById.set(parent[relation.ownerKey], parent);
|
|
1053
|
+
parentsById.set(relationMatchKey(parent[relation.ownerKey]), parent);
|
|
923
1054
|
}
|
|
924
1055
|
const result = new Map;
|
|
925
1056
|
for (const child of children) {
|
|
926
1057
|
const foreignKey = child[relation.foreignKey];
|
|
927
|
-
const parent = parentsById.get(foreignKey);
|
|
1058
|
+
const parent = parentsById.get(relationMatchKey(foreignKey));
|
|
928
1059
|
if (parent) {
|
|
929
1060
|
result.set(foreignKey, parent);
|
|
930
1061
|
}
|
|
@@ -934,23 +1065,33 @@ function indexBelongsToRelation(children, parents, relation) {
|
|
|
934
1065
|
function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
|
|
935
1066
|
const relatedById = new Map;
|
|
936
1067
|
for (const related of relatedRows) {
|
|
937
|
-
relatedById.set(related[relation.relatedKey], related);
|
|
1068
|
+
relatedById.set(relationMatchKey(related[relation.relatedKey]), related);
|
|
938
1069
|
}
|
|
939
1070
|
const groups = new Map;
|
|
1071
|
+
const originalKeys = new Map;
|
|
940
1072
|
for (const parent of parents) {
|
|
941
|
-
|
|
1073
|
+
const key = relationMatchKey(parent[relation.parentKey]);
|
|
1074
|
+
if (!groups.has(key)) {
|
|
1075
|
+
groups.set(key, []);
|
|
1076
|
+
originalKeys.set(key, parent[relation.parentKey]);
|
|
1077
|
+
}
|
|
942
1078
|
}
|
|
943
1079
|
for (const pivot of pivotRows) {
|
|
944
|
-
const
|
|
945
|
-
const
|
|
946
|
-
const group = groups.get(parentId);
|
|
947
|
-
const related = relatedById.get(relatedId);
|
|
1080
|
+
const group = groups.get(relationMatchKey(pivot[relation.foreignPivotKey]));
|
|
1081
|
+
const related = relatedById.get(relationMatchKey(pivot[relation.relatedPivotKey]));
|
|
948
1082
|
if (!group || !related) {
|
|
949
1083
|
continue;
|
|
950
1084
|
}
|
|
951
1085
|
group.push(related);
|
|
952
1086
|
}
|
|
953
|
-
|
|
1087
|
+
const result = new Map;
|
|
1088
|
+
for (const [key, group] of groups) {
|
|
1089
|
+
const original = originalKeys.get(key);
|
|
1090
|
+
if (original !== undefined) {
|
|
1091
|
+
result.set(original, group);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return result;
|
|
954
1095
|
}
|
|
955
1096
|
function morphMany(definition) {
|
|
956
1097
|
return {
|
|
@@ -972,27 +1113,38 @@ function morphTo(definition) {
|
|
|
972
1113
|
}
|
|
973
1114
|
function indexMorphManyRelation(parents, children, relation) {
|
|
974
1115
|
const groups = new Map;
|
|
1116
|
+
const originalKeys = new Map;
|
|
975
1117
|
for (const parent of parents) {
|
|
976
|
-
|
|
1118
|
+
const key = relationMatchKey(parent[relation.localKey]);
|
|
1119
|
+
if (!groups.has(key)) {
|
|
1120
|
+
groups.set(key, []);
|
|
1121
|
+
originalKeys.set(key, parent[relation.localKey]);
|
|
1122
|
+
}
|
|
977
1123
|
}
|
|
978
1124
|
for (const child of children) {
|
|
979
1125
|
if (child[relation.morphTypeKey] !== relation.morphType) {
|
|
980
1126
|
continue;
|
|
981
1127
|
}
|
|
982
|
-
const
|
|
983
|
-
const group = groups.get(key);
|
|
1128
|
+
const group = groups.get(relationMatchKey(child[relation.morphIdKey]));
|
|
984
1129
|
if (!group) {
|
|
985
1130
|
continue;
|
|
986
1131
|
}
|
|
987
1132
|
group.push(child);
|
|
988
1133
|
}
|
|
989
|
-
|
|
1134
|
+
const result = new Map;
|
|
1135
|
+
for (const [key, group] of groups) {
|
|
1136
|
+
const original = originalKeys.get(key);
|
|
1137
|
+
if (original !== undefined) {
|
|
1138
|
+
result.set(original, group);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return result;
|
|
990
1142
|
}
|
|
991
1143
|
function indexMorphOneRelation(parents, children, relation) {
|
|
992
1144
|
const grouped = indexMorphManyRelation(parents, children, relation);
|
|
993
1145
|
const result = new Map;
|
|
994
1146
|
for (const parent of parents) {
|
|
995
|
-
const matches = grouped
|
|
1147
|
+
const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
|
|
996
1148
|
result.set(parent[relation.localKey], matches[0]);
|
|
997
1149
|
}
|
|
998
1150
|
return result;
|
|
@@ -1005,7 +1157,7 @@ function indexMorphToRelation(children, parentsByType, relation) {
|
|
|
1005
1157
|
if (!parents) {
|
|
1006
1158
|
continue;
|
|
1007
1159
|
}
|
|
1008
|
-
const parent = parents
|
|
1160
|
+
const parent = getByRelationKey(parents, child[relation.morphIdKey]);
|
|
1009
1161
|
if (parent) {
|
|
1010
1162
|
result.set(child[relation.morphIdKey], parent);
|
|
1011
1163
|
}
|
|
@@ -1015,6 +1167,7 @@ function indexMorphToRelation(children, parentsByType, relation) {
|
|
|
1015
1167
|
|
|
1016
1168
|
// ../../src/core/database/model.ts
|
|
1017
1169
|
var modelRepositories = new WeakMap;
|
|
1170
|
+
var namedModels = new Map;
|
|
1018
1171
|
var modelGlobalScopes = new WeakMap;
|
|
1019
1172
|
var modelObservers = new WeakMap;
|
|
1020
1173
|
var modelBooted = new WeakSet;
|
|
@@ -1032,38 +1185,59 @@ function accessorName(key) {
|
|
|
1032
1185
|
const pascal = key.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
1033
1186
|
return `get${pascal.charAt(0).toUpperCase()}${pascal.slice(1)}Attribute`;
|
|
1034
1187
|
}
|
|
1035
|
-
function
|
|
1036
|
-
|
|
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);
|
|
1188
|
+
function isLoadableModel(value) {
|
|
1189
|
+
return Boolean(value && typeof value === "object" && typeof value.load === "function" && typeof value.loaded === "function" && typeof value.setLoaded === "function");
|
|
1049
1190
|
}
|
|
1050
|
-
async function
|
|
1051
|
-
|
|
1052
|
-
if (!head) {
|
|
1191
|
+
async function eagerLoadOnModels(models, paths) {
|
|
1192
|
+
if (models.length === 0 || paths.length === 0) {
|
|
1053
1193
|
return;
|
|
1054
1194
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1195
|
+
const grouped = new Map;
|
|
1196
|
+
for (const path of paths) {
|
|
1197
|
+
const [head, ...rest] = path.split(".");
|
|
1198
|
+
if (!head) {
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
const nested = rest.join(".");
|
|
1202
|
+
const existing = grouped.get(head) ?? [];
|
|
1203
|
+
if (nested) {
|
|
1204
|
+
existing.push(nested);
|
|
1205
|
+
}
|
|
1206
|
+
grouped.set(head, existing);
|
|
1058
1207
|
}
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1208
|
+
for (const [head, nested] of grouped) {
|
|
1209
|
+
const unloaded = models.filter((model) => model.loaded(head) === undefined);
|
|
1210
|
+
if (unloaded.length > 0) {
|
|
1211
|
+
const first = unloaded[0];
|
|
1212
|
+
if (!first) {
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1215
|
+
const method = first[head];
|
|
1216
|
+
if (typeof method !== "function") {
|
|
1217
|
+
throw new Error(`${first.constructor.name} has no relation method ${head}().`);
|
|
1218
|
+
}
|
|
1219
|
+
const relationQuery = method.call(first);
|
|
1220
|
+
const query = first.getRepository().query();
|
|
1221
|
+
relationQuery.applyEagerLoad(query, head);
|
|
1222
|
+
const attached = await query.attachToRows(unloaded.map((model) => model.toObject()));
|
|
1223
|
+
for (const [index, model] of unloaded.entries()) {
|
|
1224
|
+
const row = attached[index] ?? model.toObject();
|
|
1225
|
+
model.setLoaded(head, relationQuery.hydrateEager(row, head));
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
if (nested.length === 0) {
|
|
1229
|
+
continue;
|
|
1064
1230
|
}
|
|
1231
|
+
const children = models.flatMap((model) => {
|
|
1232
|
+
const loaded = model.loaded(head);
|
|
1233
|
+
return Array.isArray(loaded) ? loaded : loaded ? [loaded] : [];
|
|
1234
|
+
});
|
|
1235
|
+
await eagerLoadOnModels(children.filter(isLoadableModel), nested);
|
|
1065
1236
|
}
|
|
1066
1237
|
}
|
|
1238
|
+
async function loadNested(model, path) {
|
|
1239
|
+
await eagerLoadOnModels([model], [path]);
|
|
1240
|
+
}
|
|
1067
1241
|
function resolveModelRepository(model) {
|
|
1068
1242
|
const repository = modelRepositories.get(model);
|
|
1069
1243
|
if (!repository) {
|
|
@@ -1071,6 +1245,48 @@ function resolveModelRepository(model) {
|
|
|
1071
1245
|
}
|
|
1072
1246
|
return repository;
|
|
1073
1247
|
}
|
|
1248
|
+
function registerModelClass(name, model) {
|
|
1249
|
+
namedModels.set(name, model);
|
|
1250
|
+
}
|
|
1251
|
+
function resolveRelated(related) {
|
|
1252
|
+
if (typeof related === "string") {
|
|
1253
|
+
const found = namedModels.get(related);
|
|
1254
|
+
if (!found) {
|
|
1255
|
+
throw new Error(`Model [${related}] is not registered. Call registerModelClass() first.`);
|
|
1256
|
+
}
|
|
1257
|
+
return found;
|
|
1258
|
+
}
|
|
1259
|
+
if (typeof related === "function" && typeof related.repository !== "function") {
|
|
1260
|
+
return related();
|
|
1261
|
+
}
|
|
1262
|
+
return related;
|
|
1263
|
+
}
|
|
1264
|
+
function inferRelationMethodName(callee) {
|
|
1265
|
+
const stack = new Error().stack ?? "";
|
|
1266
|
+
let seenCallee = false;
|
|
1267
|
+
for (const line of stack.split(`
|
|
1268
|
+
`)) {
|
|
1269
|
+
const match = /at (?:async )?(?:[^.\s]+\.)?(\w+)/.exec(line);
|
|
1270
|
+
const name = match?.[1];
|
|
1271
|
+
if (!name || name === "Error" || name === "inferRelationMethodName") {
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
if (!seenCallee) {
|
|
1275
|
+
if (name === callee) {
|
|
1276
|
+
seenCallee = true;
|
|
1277
|
+
}
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
if (name !== callee) {
|
|
1281
|
+
return name;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
function morphClassOf(model) {
|
|
1287
|
+
const statics = modelStatics(model.constructor === Function ? model : model.constructor);
|
|
1288
|
+
return statics.$morphClass ?? (model.constructor === Function ? model.name : model.constructor.name);
|
|
1289
|
+
}
|
|
1074
1290
|
function modelStatics(model) {
|
|
1075
1291
|
return model;
|
|
1076
1292
|
}
|
|
@@ -1100,6 +1316,11 @@ function hydrateValue(value, cast) {
|
|
|
1100
1316
|
case "bool":
|
|
1101
1317
|
case "boolean":
|
|
1102
1318
|
return value === true || value === 1 || value === "1" || value === "true";
|
|
1319
|
+
case "integer":
|
|
1320
|
+
case "int":
|
|
1321
|
+
return value === "" ? null : Number(value);
|
|
1322
|
+
case "hashed":
|
|
1323
|
+
return value;
|
|
1103
1324
|
default:
|
|
1104
1325
|
return value;
|
|
1105
1326
|
}
|
|
@@ -1117,6 +1338,11 @@ function dehydrateValue(value, cast) {
|
|
|
1117
1338
|
case "bool":
|
|
1118
1339
|
case "boolean":
|
|
1119
1340
|
return Boolean(value);
|
|
1341
|
+
case "integer":
|
|
1342
|
+
case "int":
|
|
1343
|
+
return value === "" ? null : Number(value);
|
|
1344
|
+
case "hashed":
|
|
1345
|
+
return value;
|
|
1120
1346
|
default:
|
|
1121
1347
|
return value;
|
|
1122
1348
|
}
|
|
@@ -1171,6 +1397,153 @@ function applyTimestampsOnUpdate(columns, values, enabled) {
|
|
|
1171
1397
|
return result;
|
|
1172
1398
|
}
|
|
1173
1399
|
|
|
1400
|
+
class ModelQuery {
|
|
1401
|
+
modelClass;
|
|
1402
|
+
query;
|
|
1403
|
+
eager = [];
|
|
1404
|
+
constructor(modelClass, query) {
|
|
1405
|
+
this.modelClass = modelClass;
|
|
1406
|
+
this.query = query;
|
|
1407
|
+
}
|
|
1408
|
+
with(...relations) {
|
|
1409
|
+
const statics = modelStatics(this.modelClass);
|
|
1410
|
+
ensureBooted(this.modelClass);
|
|
1411
|
+
const dummy = statics.newFromRecord({}, false);
|
|
1412
|
+
for (const path of relations) {
|
|
1413
|
+
const name = path.split(".")[0] ?? path;
|
|
1414
|
+
const method = dummy[name];
|
|
1415
|
+
if (typeof method !== "function") {
|
|
1416
|
+
throw new Error(`${this.modelClass.name} has no relation method ${name}().`);
|
|
1417
|
+
}
|
|
1418
|
+
const relationQuery = method.call(dummy);
|
|
1419
|
+
this.eager.push({ name, path, relationQuery });
|
|
1420
|
+
relationQuery.applyEagerLoad(this.query, name);
|
|
1421
|
+
}
|
|
1422
|
+
return this;
|
|
1423
|
+
}
|
|
1424
|
+
where(input) {
|
|
1425
|
+
this.query.where(input);
|
|
1426
|
+
return this;
|
|
1427
|
+
}
|
|
1428
|
+
orWhere(input) {
|
|
1429
|
+
this.query.orWhere(input);
|
|
1430
|
+
return this;
|
|
1431
|
+
}
|
|
1432
|
+
orderBy(orderBy) {
|
|
1433
|
+
this.query.orderBy(orderBy);
|
|
1434
|
+
return this;
|
|
1435
|
+
}
|
|
1436
|
+
limit(limit) {
|
|
1437
|
+
this.query.limit(limit);
|
|
1438
|
+
return this;
|
|
1439
|
+
}
|
|
1440
|
+
offset(offset) {
|
|
1441
|
+
this.query.offset(offset);
|
|
1442
|
+
return this;
|
|
1443
|
+
}
|
|
1444
|
+
whereNull(column) {
|
|
1445
|
+
this.query.whereNull(column);
|
|
1446
|
+
return this;
|
|
1447
|
+
}
|
|
1448
|
+
whereIn(column, values) {
|
|
1449
|
+
this.query.whereIn(column, values);
|
|
1450
|
+
return this;
|
|
1451
|
+
}
|
|
1452
|
+
whereExists(sql, params = []) {
|
|
1453
|
+
this.query.whereExists(sql, params);
|
|
1454
|
+
return this;
|
|
1455
|
+
}
|
|
1456
|
+
whereNotExists(sql, params = []) {
|
|
1457
|
+
this.query.whereNotExists(sql, params);
|
|
1458
|
+
return this;
|
|
1459
|
+
}
|
|
1460
|
+
whereHas(name, constrain) {
|
|
1461
|
+
return this.constrainExists(name, constrain, false);
|
|
1462
|
+
}
|
|
1463
|
+
has(name) {
|
|
1464
|
+
return this.constrainExists(name, undefined, false);
|
|
1465
|
+
}
|
|
1466
|
+
doesntHave(name) {
|
|
1467
|
+
return this.constrainExists(name, undefined, true);
|
|
1468
|
+
}
|
|
1469
|
+
whereDoesntHave(name, constrain) {
|
|
1470
|
+
return this.constrainExists(name, constrain, true);
|
|
1471
|
+
}
|
|
1472
|
+
withHasMany(...args) {
|
|
1473
|
+
this.query.withHasMany(...args);
|
|
1474
|
+
return this;
|
|
1475
|
+
}
|
|
1476
|
+
withBelongsTo(...args) {
|
|
1477
|
+
this.query.withBelongsTo(...args);
|
|
1478
|
+
return this;
|
|
1479
|
+
}
|
|
1480
|
+
withBelongsToMany(...args) {
|
|
1481
|
+
this.query.withBelongsToMany(...args);
|
|
1482
|
+
return this;
|
|
1483
|
+
}
|
|
1484
|
+
withMorphMany(...args) {
|
|
1485
|
+
this.query.withMorphMany(...args);
|
|
1486
|
+
return this;
|
|
1487
|
+
}
|
|
1488
|
+
withMorphOne(...args) {
|
|
1489
|
+
this.query.withMorphOne(...args);
|
|
1490
|
+
return this;
|
|
1491
|
+
}
|
|
1492
|
+
withMorphTo(...args) {
|
|
1493
|
+
this.query.withMorphTo(...args);
|
|
1494
|
+
return this;
|
|
1495
|
+
}
|
|
1496
|
+
async get() {
|
|
1497
|
+
const statics = modelStatics(this.modelClass);
|
|
1498
|
+
const rows = await this.query.get();
|
|
1499
|
+
const models = [];
|
|
1500
|
+
for (const row of rows) {
|
|
1501
|
+
const model = statics.newFromRecord(row, true);
|
|
1502
|
+
await runObservers(model, "retrieved");
|
|
1503
|
+
for (const { name, relationQuery } of this.eager) {
|
|
1504
|
+
model.setLoaded(name, relationQuery.hydrateEager(row, name));
|
|
1505
|
+
}
|
|
1506
|
+
models.push(model);
|
|
1507
|
+
}
|
|
1508
|
+
const nested = this.eager.filter((item) => item.path.includes(".")).map((item) => item.path);
|
|
1509
|
+
await eagerLoadOnModels(models.filter(isLoadableModel), nested);
|
|
1510
|
+
return models;
|
|
1511
|
+
}
|
|
1512
|
+
async first() {
|
|
1513
|
+
this.query.limit(1);
|
|
1514
|
+
const models = await this.get();
|
|
1515
|
+
return models[0] ?? null;
|
|
1516
|
+
}
|
|
1517
|
+
async find(id) {
|
|
1518
|
+
const primaryKey = resolveModelRepository(this.modelClass).getTable().primaryKey;
|
|
1519
|
+
return this.where({ [primaryKey]: id }).first();
|
|
1520
|
+
}
|
|
1521
|
+
async findOrFail(id, errorFactory) {
|
|
1522
|
+
const model = await this.find(id);
|
|
1523
|
+
if (model) {
|
|
1524
|
+
return model;
|
|
1525
|
+
}
|
|
1526
|
+
throw errorFactory?.(id) ?? new NotFoundError(`${this.modelClass.name} ${String(id)} not found.`);
|
|
1527
|
+
}
|
|
1528
|
+
then(onfulfilled, onrejected) {
|
|
1529
|
+
return this.get().then(onfulfilled ?? undefined, onrejected ?? undefined);
|
|
1530
|
+
}
|
|
1531
|
+
constrainExists(name, constrain, not) {
|
|
1532
|
+
const statics = modelStatics(this.modelClass);
|
|
1533
|
+
ensureBooted(this.modelClass);
|
|
1534
|
+
const repository = resolveModelRepository(this.modelClass);
|
|
1535
|
+
const dummy = statics.newFromRecord({});
|
|
1536
|
+
const method = dummy[name];
|
|
1537
|
+
if (typeof method !== "function") {
|
|
1538
|
+
throw new Error(`${this.modelClass.name} has no relation method ${name}().`);
|
|
1539
|
+
}
|
|
1540
|
+
const relationQuery = method.call(dummy);
|
|
1541
|
+
constrain?.(relationQuery);
|
|
1542
|
+
const exists = relationQuery.toExistsClause(repository.getTable().name);
|
|
1543
|
+
return not ? this.whereNotExists(exists.sql, exists.params) : this.whereExists(exists.sql, exists.params);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1174
1547
|
class Model {
|
|
1175
1548
|
attributes;
|
|
1176
1549
|
repository;
|
|
@@ -1181,6 +1554,7 @@ class Model {
|
|
|
1181
1554
|
static $hidden;
|
|
1182
1555
|
static $visible;
|
|
1183
1556
|
static $appends;
|
|
1557
|
+
static $morphClass;
|
|
1184
1558
|
_exists;
|
|
1185
1559
|
loadedRelations = {};
|
|
1186
1560
|
hiddenOverrides = [];
|
|
@@ -1190,6 +1564,7 @@ class Model {
|
|
|
1190
1564
|
this.attributes = attributes;
|
|
1191
1565
|
this.repository = repository;
|
|
1192
1566
|
this._exists = exists;
|
|
1567
|
+
this.attributes = modelStatics(this.constructor).hydrateAttributes(attributes);
|
|
1193
1568
|
}
|
|
1194
1569
|
getRepository() {
|
|
1195
1570
|
return this.repository;
|
|
@@ -1250,7 +1625,7 @@ class Model {
|
|
|
1250
1625
|
return this;
|
|
1251
1626
|
}
|
|
1252
1627
|
primaryKey() {
|
|
1253
|
-
|
|
1628
|
+
return this.repository.getTable().primaryKey;
|
|
1254
1629
|
}
|
|
1255
1630
|
static primaryKeyField() {
|
|
1256
1631
|
return resolveModelRepository(this).getTable().primaryKey;
|
|
@@ -1291,7 +1666,7 @@ class Model {
|
|
|
1291
1666
|
for (const scope of getGlobalScopes(this)) {
|
|
1292
1667
|
query = scope(query);
|
|
1293
1668
|
}
|
|
1294
|
-
return query;
|
|
1669
|
+
return new ModelQuery(this, query);
|
|
1295
1670
|
}
|
|
1296
1671
|
static newFromRecord(record, exists = true) {
|
|
1297
1672
|
const repository = resolveModelRepository(this);
|
|
@@ -1310,79 +1685,36 @@ class Model {
|
|
|
1310
1685
|
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
1311
1686
|
const payload = statics.dehydrateAttributes(withTimestamps);
|
|
1312
1687
|
const pending = statics.newFromRecord({ ...payload }, false);
|
|
1688
|
+
if (await runObservers(pending, "saving") === false) {
|
|
1689
|
+
throw new Error(`${this.name}.create() was cancelled by an observer.`);
|
|
1690
|
+
}
|
|
1313
1691
|
if (await runObservers(pending, "creating") === false) {
|
|
1314
1692
|
throw new Error(`${this.name}.create() was cancelled by an observer.`);
|
|
1315
1693
|
}
|
|
1316
1694
|
const record = await repository.create(payload);
|
|
1317
1695
|
const created = statics.fromRecord(record, repository, true);
|
|
1318
1696
|
await runObservers(created, "created");
|
|
1697
|
+
await runObservers(created, "saved");
|
|
1319
1698
|
return created;
|
|
1320
1699
|
}
|
|
1321
1700
|
static with(...relations) {
|
|
1322
|
-
|
|
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
|
-
};
|
|
1701
|
+
return Model.query.call(this).with(...relations);
|
|
1367
1702
|
}
|
|
1368
1703
|
static whereHas(name, constrain) {
|
|
1369
|
-
return
|
|
1704
|
+
return Model.query.call(this).whereHas(name, constrain);
|
|
1370
1705
|
}
|
|
1371
1706
|
static has(name) {
|
|
1372
|
-
return
|
|
1707
|
+
return Model.query.call(this).has(name);
|
|
1373
1708
|
}
|
|
1374
1709
|
static doesntHave(name) {
|
|
1375
|
-
return
|
|
1710
|
+
return Model.query.call(this).doesntHave(name);
|
|
1376
1711
|
}
|
|
1377
1712
|
static whereDoesntHave(name, constrain) {
|
|
1378
|
-
return
|
|
1713
|
+
return Model.query.call(this).whereDoesntHave(name, constrain);
|
|
1379
1714
|
}
|
|
1380
1715
|
static async find(id) {
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
const primaryKey = repository.getTable().primaryKey;
|
|
1384
|
-
const record = await Model.query.call(this).where({ [primaryKey]: id }).first();
|
|
1385
|
-
return record ? statics.fromRecord(record, repository, true) : null;
|
|
1716
|
+
const primaryKey = resolveModelRepository(this).getTable().primaryKey;
|
|
1717
|
+
return Model.query.call(this).where({ [primaryKey]: id }).first();
|
|
1386
1718
|
}
|
|
1387
1719
|
static async findOrFail(id, errorFactory) {
|
|
1388
1720
|
const model = await Model.find.call(this, id);
|
|
@@ -1392,8 +1724,6 @@ class Model {
|
|
|
1392
1724
|
throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
|
|
1393
1725
|
}
|
|
1394
1726
|
static async all(options = {}) {
|
|
1395
|
-
const statics = modelStatics(this);
|
|
1396
|
-
const repository = resolveModelRepository(this);
|
|
1397
1727
|
let query = Model.query.call(this);
|
|
1398
1728
|
if (options.orderBy) {
|
|
1399
1729
|
query = query.orderBy(options.orderBy);
|
|
@@ -1401,21 +1731,17 @@ class Model {
|
|
|
1401
1731
|
if (options.limit !== undefined) {
|
|
1402
1732
|
query = query.limit(options.limit);
|
|
1403
1733
|
}
|
|
1404
|
-
|
|
1405
|
-
return rows.map((row) => statics.fromRecord(row, repository, true));
|
|
1734
|
+
return query.get();
|
|
1406
1735
|
}
|
|
1407
1736
|
static where(where) {
|
|
1408
1737
|
return Model.query.call(this).where(where);
|
|
1409
1738
|
}
|
|
1410
1739
|
static async firstWhere(where, options = {}) {
|
|
1411
|
-
const statics = modelStatics(this);
|
|
1412
|
-
const repository = resolveModelRepository(this);
|
|
1413
1740
|
let query = Model.query.call(this).where(where);
|
|
1414
1741
|
if (options.orderBy) {
|
|
1415
1742
|
query = query.orderBy(options.orderBy);
|
|
1416
1743
|
}
|
|
1417
|
-
|
|
1418
|
-
return record ? statics.fromRecord(record, repository, true) : null;
|
|
1744
|
+
return query.first();
|
|
1419
1745
|
}
|
|
1420
1746
|
static async firstOrNew(where, values = {}) {
|
|
1421
1747
|
const existing = await Model.firstWhere.call(this, where);
|
|
@@ -1444,6 +1770,9 @@ class Model {
|
|
|
1444
1770
|
const casts = ModelClass.$casts ?? {};
|
|
1445
1771
|
const table = this.repository.getTable();
|
|
1446
1772
|
const updating = this.$exists;
|
|
1773
|
+
if (await runObservers(this, "saving") === false) {
|
|
1774
|
+
return this;
|
|
1775
|
+
}
|
|
1447
1776
|
if (await runObservers(this, updating ? "updating" : "creating") === false) {
|
|
1448
1777
|
return this;
|
|
1449
1778
|
}
|
|
@@ -1452,6 +1781,7 @@ class Model {
|
|
|
1452
1781
|
const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
|
|
1453
1782
|
this.attributes = ModelClass.hydrateAttributes(record2);
|
|
1454
1783
|
await runObservers(this, "updated");
|
|
1784
|
+
await runObservers(this, "saved");
|
|
1455
1785
|
return this;
|
|
1456
1786
|
}
|
|
1457
1787
|
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
|
|
@@ -1461,6 +1791,7 @@ class Model {
|
|
|
1461
1791
|
this.attributes = ModelClass.hydrateAttributes(record);
|
|
1462
1792
|
this._exists = true;
|
|
1463
1793
|
await runObservers(this, "created");
|
|
1794
|
+
await runObservers(this, "saved");
|
|
1464
1795
|
return this;
|
|
1465
1796
|
}
|
|
1466
1797
|
async update(changes) {
|
|
@@ -1493,7 +1824,7 @@ class Model {
|
|
|
1493
1824
|
}
|
|
1494
1825
|
async loadHasMany(as, relation, childRepository, options = {}) {
|
|
1495
1826
|
const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
|
|
1496
|
-
const loaded = grouped
|
|
1827
|
+
const loaded = getByRelationKey(grouped, this.attributes[relation.localKey]) ?? [];
|
|
1497
1828
|
return Object.assign(this, { [as]: loaded });
|
|
1498
1829
|
}
|
|
1499
1830
|
async loadHasOne(as, relation, childRepository, options = {}) {
|
|
@@ -1503,7 +1834,7 @@ class Model {
|
|
|
1503
1834
|
}
|
|
1504
1835
|
async loadBelongsTo(as, relation, parentRepository, options = {}) {
|
|
1505
1836
|
const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
|
|
1506
|
-
const loaded = grouped
|
|
1837
|
+
const loaded = getByRelationKey(grouped, this.attributes[relation.foreignKey]);
|
|
1507
1838
|
return Object.assign(this, { [as]: loaded });
|
|
1508
1839
|
}
|
|
1509
1840
|
async loadBelongsToMany(as, relation, relatedRepository, options = {}) {
|
|
@@ -1523,28 +1854,31 @@ class Model {
|
|
|
1523
1854
|
}
|
|
1524
1855
|
});
|
|
1525
1856
|
const grouped = indexBelongsToManyRelation([this.attributes], pivotRows, relatedRows, relation);
|
|
1526
|
-
const loaded = grouped
|
|
1857
|
+
const loaded = getByRelationKey(grouped, parentId) ?? [];
|
|
1527
1858
|
return Object.assign(this, { [as]: loaded });
|
|
1528
1859
|
}
|
|
1529
1860
|
hasMany(related, foreignKey, localKey) {
|
|
1530
1861
|
const table = this.repository.getTable();
|
|
1531
|
-
|
|
1532
|
-
|
|
1862
|
+
const relatedClass = resolveRelated(related);
|
|
1863
|
+
return new HasManyRelationQuery(this, relatedClass, hasMany({
|
|
1864
|
+
name: relatedClass.repository().getTable().name,
|
|
1533
1865
|
localKey: localKey ?? table.primaryKey,
|
|
1534
1866
|
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
1535
1867
|
}));
|
|
1536
1868
|
}
|
|
1537
1869
|
hasOne(related, foreignKey, localKey) {
|
|
1538
1870
|
const table = this.repository.getTable();
|
|
1539
|
-
|
|
1540
|
-
|
|
1871
|
+
const relatedClass = resolveRelated(related);
|
|
1872
|
+
return new HasOneRelationQuery(this, relatedClass, hasOne({
|
|
1873
|
+
name: relatedClass.repository().getTable().name,
|
|
1541
1874
|
localKey: localKey ?? table.primaryKey,
|
|
1542
1875
|
foreignKey: foreignKey ?? foreignKeyFromTable(table.name)
|
|
1543
1876
|
}));
|
|
1544
1877
|
}
|
|
1545
1878
|
belongsTo(related, foreignKey, ownerKey) {
|
|
1546
|
-
const
|
|
1547
|
-
|
|
1879
|
+
const relatedClass = resolveRelated(related);
|
|
1880
|
+
const relatedTable = relatedClass.repository().getTable();
|
|
1881
|
+
return new BelongsToRelationQuery(this, relatedClass, belongsTo({
|
|
1548
1882
|
name: relatedTable.name,
|
|
1549
1883
|
foreignKey: foreignKey ?? foreignKeyFromTable(relatedTable.name),
|
|
1550
1884
|
ownerKey: ownerKey ?? relatedTable.primaryKey
|
|
@@ -1552,8 +1886,9 @@ class Model {
|
|
|
1552
1886
|
}
|
|
1553
1887
|
belongsToMany(related, pivotTable, foreignPivotKey, relatedPivotKey) {
|
|
1554
1888
|
const table = this.repository.getTable();
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1889
|
+
const relatedClass = resolveRelated(related);
|
|
1890
|
+
const relatedTable = relatedClass.repository().getTable();
|
|
1891
|
+
return new BelongsToManyRelationQuery(this, relatedClass, belongsToMany({
|
|
1557
1892
|
name: relatedTable.name,
|
|
1558
1893
|
pivotTable: pivotTable ?? pivotTableName(table.name, relatedTable.name),
|
|
1559
1894
|
parentKey: table.primaryKey,
|
|
@@ -1562,31 +1897,38 @@ class Model {
|
|
|
1562
1897
|
relatedPivotKey: relatedPivotKey ?? foreignKeyFromTable(relatedTable.name)
|
|
1563
1898
|
}));
|
|
1564
1899
|
}
|
|
1565
|
-
morphMany(related, morphName, typeKey, idKey) {
|
|
1900
|
+
morphMany(related, morphName, typeKey, idKey, morphType) {
|
|
1566
1901
|
const table = this.repository.getTable();
|
|
1567
|
-
|
|
1902
|
+
const relatedClass = resolveRelated(related);
|
|
1903
|
+
return new MorphManyRelationQuery(this, relatedClass, morphMany({
|
|
1568
1904
|
name: morphName,
|
|
1569
1905
|
localKey: table.primaryKey,
|
|
1570
1906
|
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
1571
1907
|
morphIdKey: idKey ?? `${morphName}_id`,
|
|
1572
|
-
morphType:
|
|
1908
|
+
morphType: morphType ?? morphClassOf(this)
|
|
1573
1909
|
}));
|
|
1574
1910
|
}
|
|
1575
|
-
morphOne(related, morphName, typeKey, idKey) {
|
|
1911
|
+
morphOne(related, morphName, typeKey, idKey, morphType) {
|
|
1576
1912
|
const table = this.repository.getTable();
|
|
1577
|
-
|
|
1913
|
+
const relatedClass = resolveRelated(related);
|
|
1914
|
+
return new MorphOneRelationQuery(this, relatedClass, morphOne({
|
|
1578
1915
|
name: morphName,
|
|
1579
1916
|
localKey: table.primaryKey,
|
|
1580
1917
|
morphTypeKey: typeKey ?? `${morphName}_type`,
|
|
1581
1918
|
morphIdKey: idKey ?? `${morphName}_id`,
|
|
1582
|
-
morphType:
|
|
1919
|
+
morphType: morphType ?? morphClassOf(this)
|
|
1583
1920
|
}));
|
|
1584
1921
|
}
|
|
1585
|
-
morphTo(relatedByType, morphName
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1922
|
+
morphTo(relatedByType, morphName, typeKey, idKey) {
|
|
1923
|
+
const resolvedName = morphName ?? inferRelationMethodName("morphTo");
|
|
1924
|
+
if (!resolvedName) {
|
|
1925
|
+
throw new Error(`${this.constructor.name}.morphTo() needs an explicit morph name (Laravel infers it from the relation method).`);
|
|
1926
|
+
}
|
|
1927
|
+
const resolvedMap = Object.fromEntries(Object.entries(relatedByType).map(([type, related]) => [type, resolveRelated(related)]));
|
|
1928
|
+
return new MorphToRelationQuery(this, resolvedMap, morphTo({
|
|
1929
|
+
name: resolvedName,
|
|
1930
|
+
morphTypeKey: typeKey ?? `${resolvedName}_type`,
|
|
1931
|
+
morphIdKey: idKey ?? `${resolvedName}_id`
|
|
1590
1932
|
}));
|
|
1591
1933
|
}
|
|
1592
1934
|
async load(...names) {
|
|
@@ -1618,6 +1960,14 @@ class Model {
|
|
|
1618
1960
|
}
|
|
1619
1961
|
function registerModelRepository(model, repository) {
|
|
1620
1962
|
modelRepositories.set(model, repository);
|
|
1963
|
+
const name = model.name;
|
|
1964
|
+
if (name) {
|
|
1965
|
+
namedModels.set(name, model);
|
|
1966
|
+
}
|
|
1967
|
+
const morphClass = model.$morphClass;
|
|
1968
|
+
if (morphClass) {
|
|
1969
|
+
namedModels.set(morphClass, model);
|
|
1970
|
+
}
|
|
1621
1971
|
ensureBooted(model);
|
|
1622
1972
|
return model;
|
|
1623
1973
|
}
|
|
@@ -1627,6 +1977,7 @@ export {
|
|
|
1627
1977
|
HasManyRelationQuery,
|
|
1628
1978
|
HasOneRelationQuery,
|
|
1629
1979
|
Model,
|
|
1980
|
+
ModelQuery,
|
|
1630
1981
|
MorphManyRelationQuery,
|
|
1631
1982
|
MorphOneRelationQuery,
|
|
1632
1983
|
MorphToRelationQuery,
|
|
@@ -1634,5 +1985,6 @@ export {
|
|
|
1634
1985
|
dehydrateValue,
|
|
1635
1986
|
filterMassAssignable,
|
|
1636
1987
|
hydrateValue,
|
|
1988
|
+
registerModelClass,
|
|
1637
1989
|
registerModelRepository
|
|
1638
1990
|
};
|