@bunnykit/orm 0.1.26 → 0.1.27
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 +42 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/model/Model.d.ts +11 -4
- package/dist/src/model/Model.js +49 -18
- package/dist/src/query/Builder.d.ts +4 -3
- package/dist/src/query/Builder.js +18 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -972,6 +972,48 @@ class User extends Model {
|
|
|
972
972
|
const post = await user.latestPost().getResults();
|
|
973
973
|
```
|
|
974
974
|
|
|
975
|
+
### Eager Loading
|
|
976
|
+
|
|
977
|
+
Use `with()` to eager load relations for query results:
|
|
978
|
+
|
|
979
|
+
```ts
|
|
980
|
+
const users = await User.with("posts", "profile").get();
|
|
981
|
+
const posts = await Post.with("author").get();
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
Nested relations use dot notation:
|
|
985
|
+
|
|
986
|
+
```ts
|
|
987
|
+
const users = await User.with("posts.comments").get();
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
You can constrain an eager-loaded relation by passing an object whose key is the relation name and whose value is a query callback:
|
|
991
|
+
|
|
992
|
+
```ts
|
|
993
|
+
const users = await User.with({
|
|
994
|
+
posts: (query) => {
|
|
995
|
+
query.where("status", "published").orderBy("created_at", "desc");
|
|
996
|
+
},
|
|
997
|
+
}).get();
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
Nested eager loads can be constrained too:
|
|
1001
|
+
|
|
1002
|
+
```ts
|
|
1003
|
+
const users = await User.with(
|
|
1004
|
+
{ posts: (query) => query.where("status", "published") },
|
|
1005
|
+
{ "posts.comments": (query) => query.where("approved", true) },
|
|
1006
|
+
).get();
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
Constrained eager loading is also available when loading relations onto an existing model:
|
|
1010
|
+
|
|
1011
|
+
```ts
|
|
1012
|
+
await user.load({
|
|
1013
|
+
posts: (query) => query.where("status", "published"),
|
|
1014
|
+
});
|
|
1015
|
+
```
|
|
1016
|
+
|
|
975
1017
|
### Relation Queries and Aggregates
|
|
976
1018
|
|
|
977
1019
|
Filter models by related records:
|
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, GlobalScope, CastDefinition, CastsAttributes, } from "./model/Model.js";
|
|
17
|
+
export type { ModelAttributeInput, ModelAttributes, 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";
|
|
@@ -5,6 +5,12 @@ import { BelongsToMany } from "./BelongsToMany.js";
|
|
|
5
5
|
export type ModelConstructor<T extends Model = Model> = (new (...args: any[]) => T) & Omit<typeof Model, "prototype">;
|
|
6
6
|
export type GlobalScope = (builder: Builder<any>, model: ModelConstructor) => void;
|
|
7
7
|
export type LiteralUnion<T extends string> = T | (string & {});
|
|
8
|
+
export type EagerLoadConstraint = (query: Builder<any>) => void | Builder<any>;
|
|
9
|
+
export interface EagerLoadDefinition {
|
|
10
|
+
name: string;
|
|
11
|
+
constraint?: EagerLoadConstraint;
|
|
12
|
+
}
|
|
13
|
+
export type EagerLoadInput = string | EagerLoadDefinition | Record<string, EagerLoadConstraint | undefined>;
|
|
8
14
|
type BaseModelInstanceKey = "$attributes" | "$original" | "$exists" | "$relations" | "$casts" | "$castCache" | "$connection" | "fill" | "setConnection" | "getConnection" | "isFillable" | "getAttribute" | "setAttribute" | "castAttribute" | "serializeCastAttribute" | "mergeCasts" | "getDirty" | "isDirty" | "save" | "updateTimestamps" | "touch" | "increment" | "decrement" | "load" | "delete" | "restore" | "forceDelete" | "refresh" | "toJSON" | "toString" | "freshTimestamp" | "setRelation" | "getRelation" | "hasMany" | "belongsTo" | "hasOne" | "hasManyThrough" | "hasOneThrough" | "belongsToMany" | "morphTo" | "morphOne" | "morphMany" | "morphToMany" | "morphedByMany";
|
|
9
15
|
export type ModelInstanceAttributeKeys<T> = Extract<Exclude<keyof T, BaseModelInstanceKey>, string>;
|
|
10
16
|
export type ModelAttributes<T> = T extends {
|
|
@@ -168,7 +174,7 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
168
174
|
static inRandomOrder<M extends ModelConstructor>(this: M): Builder<InstanceType<M>>;
|
|
169
175
|
static lockForUpdate<M extends ModelConstructor>(this: M): Builder<InstanceType<M>>;
|
|
170
176
|
static sharedLock<M extends ModelConstructor>(this: M): Builder<InstanceType<M>>;
|
|
171
|
-
static with<M extends ModelConstructor>(this: M, ...relations: ModelRelationName<InstanceType<M>>[]): Builder<InstanceType<M>>;
|
|
177
|
+
static with<M extends ModelConstructor>(this: M, ...relations: (ModelRelationName<InstanceType<M>> | EagerLoadInput | EagerLoadInput[])[]): Builder<InstanceType<M>>;
|
|
172
178
|
static withTrashed<M extends ModelConstructor>(this: M): Builder<InstanceType<M>>;
|
|
173
179
|
static onlyTrashed<M extends ModelConstructor>(this: M): Builder<InstanceType<M>>;
|
|
174
180
|
static withoutGlobalScope<M extends ModelConstructor>(this: M, scope: string): Builder<InstanceType<M>>;
|
|
@@ -188,8 +194,9 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
188
194
|
static each<M extends ModelConstructor>(this: M, count: number, callback: (item: InstanceType<M>) => void | Promise<void>): Promise<void>;
|
|
189
195
|
static cursor<M extends ModelConstructor>(this: M): AsyncGenerator<InstanceType<M>>;
|
|
190
196
|
static lazy<M extends ModelConstructor>(this: M, count?: number): AsyncGenerator<InstanceType<M>>;
|
|
191
|
-
static
|
|
192
|
-
static
|
|
197
|
+
static normalizeEagerLoads(relations: (EagerLoadInput | EagerLoadInput[])[]): EagerLoadDefinition[];
|
|
198
|
+
static eagerLoadRelations(models: Model[], relations: (string | EagerLoadDefinition)[]): Promise<void>;
|
|
199
|
+
static eagerLoadRelation(models: Model[], relationName: string, constraint?: EagerLoadConstraint): Promise<void>;
|
|
193
200
|
fill(attributes: Partial<T> | ModelAttributeInput<this>): this;
|
|
194
201
|
setConnection(connection: Connection): this;
|
|
195
202
|
getConnection(): Connection;
|
|
@@ -210,7 +217,7 @@ export declare class Model<T extends Record<string, any> = Record<string, any>>
|
|
|
210
217
|
touch(): Promise<boolean>;
|
|
211
218
|
increment<K extends ModelColumn<this>>(column: K, amount?: number, extra?: ModelAttributeInput<this>): Promise<this>;
|
|
212
219
|
decrement<K extends ModelColumn<this>>(column: K, amount?: number, extra?: ModelAttributeInput<this>): Promise<this>;
|
|
213
|
-
load(...relations: string[]): Promise<this>;
|
|
220
|
+
load(...relations: (string | EagerLoadInput | EagerLoadInput[])[]): Promise<this>;
|
|
214
221
|
delete(): Promise<boolean>;
|
|
215
222
|
restore(): Promise<boolean>;
|
|
216
223
|
forceDelete(): Promise<boolean>;
|
package/dist/src/model/Model.js
CHANGED
|
@@ -629,34 +629,65 @@ export class Model {
|
|
|
629
629
|
static lazy(count) {
|
|
630
630
|
return this.query().lazy(count);
|
|
631
631
|
}
|
|
632
|
-
static
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (Array.isArray(related))
|
|
641
|
-
nestedModels.push(...related);
|
|
642
|
-
else if (related)
|
|
643
|
-
nestedModels.push(related);
|
|
644
|
-
}
|
|
645
|
-
if (nestedModels.length > 0) {
|
|
646
|
-
await this.eagerLoadRelations(nestedModels, [rest.join(".")]);
|
|
647
|
-
}
|
|
632
|
+
static normalizeEagerLoads(relations) {
|
|
633
|
+
const normalized = [];
|
|
634
|
+
for (const relation of relations.flat()) {
|
|
635
|
+
if (typeof relation === "string") {
|
|
636
|
+
normalized.push({ name: relation });
|
|
637
|
+
}
|
|
638
|
+
else if ("name" in relation && typeof relation.name === "string") {
|
|
639
|
+
normalized.push(relation);
|
|
648
640
|
}
|
|
649
641
|
else {
|
|
650
|
-
|
|
642
|
+
for (const [name, constraint] of Object.entries(relation)) {
|
|
643
|
+
normalized.push({ name, constraint });
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return normalized;
|
|
648
|
+
}
|
|
649
|
+
static async eagerLoadRelations(models, relations) {
|
|
650
|
+
const normalized = this.normalizeEagerLoads(relations);
|
|
651
|
+
const groups = new Map();
|
|
652
|
+
for (const definition of normalized) {
|
|
653
|
+
const [first] = definition.name.split(".");
|
|
654
|
+
const group = groups.get(first) || [];
|
|
655
|
+
group.push(definition);
|
|
656
|
+
groups.set(first, group);
|
|
657
|
+
}
|
|
658
|
+
for (const [relationName, definitions] of groups) {
|
|
659
|
+
const direct = definitions.find((definition) => definition.name === relationName);
|
|
660
|
+
await this.eagerLoadRelation(models, relationName, direct?.constraint);
|
|
661
|
+
const nestedDefinitions = definitions
|
|
662
|
+
.filter((definition) => definition.name.includes("."))
|
|
663
|
+
.map((definition) => ({
|
|
664
|
+
name: definition.name.split(".").slice(1).join("."),
|
|
665
|
+
constraint: definition.constraint,
|
|
666
|
+
}));
|
|
667
|
+
if (nestedDefinitions.length === 0)
|
|
668
|
+
continue;
|
|
669
|
+
const nestedModels = [];
|
|
670
|
+
for (const model of models) {
|
|
671
|
+
const related = model.getRelation(relationName);
|
|
672
|
+
if (Array.isArray(related))
|
|
673
|
+
nestedModels.push(...related);
|
|
674
|
+
else if (related)
|
|
675
|
+
nestedModels.push(related);
|
|
676
|
+
}
|
|
677
|
+
if (nestedModels.length > 0) {
|
|
678
|
+
await this.eagerLoadRelations(nestedModels, nestedDefinitions);
|
|
651
679
|
}
|
|
652
680
|
}
|
|
653
681
|
}
|
|
654
|
-
static async eagerLoadRelation(models, relationName) {
|
|
682
|
+
static async eagerLoadRelation(models, relationName, constraint) {
|
|
655
683
|
if (models.length === 0)
|
|
656
684
|
return;
|
|
657
685
|
const firstModel = models[0];
|
|
658
686
|
const relation = firstModel[relationName]();
|
|
659
687
|
relation.addEagerConstraints(models);
|
|
688
|
+
if (constraint) {
|
|
689
|
+
constraint(relation.getQuery());
|
|
690
|
+
}
|
|
660
691
|
const results = await relation.getEager();
|
|
661
692
|
relation.match(models, results, relationName);
|
|
662
693
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Connection } from "../connection/Connection.js";
|
|
2
2
|
import type { WhereClause, OrderClause, HavingClause, UnionClause } from "../types/index.js";
|
|
3
|
-
import type { ModelAttributeInput, ModelColumn, ModelColumnValue, ModelConstructor
|
|
3
|
+
import type { EagerLoadDefinition, EagerLoadInput, ModelAttributeInput, ModelColumn, ModelColumnValue, ModelConstructor } from "../model/Model.js";
|
|
4
4
|
type RelationConstraint = (query: Builder<any>) => void | Builder<any>;
|
|
5
5
|
export interface Paginator<T> {
|
|
6
6
|
data: T[];
|
|
@@ -24,7 +24,7 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
24
24
|
joins: string[];
|
|
25
25
|
distinctFlag: boolean;
|
|
26
26
|
model?: ModelConstructor;
|
|
27
|
-
eagerLoads:
|
|
27
|
+
eagerLoads: EagerLoadDefinition[];
|
|
28
28
|
randomOrderFlag: boolean;
|
|
29
29
|
lockMode?: string;
|
|
30
30
|
unions: UnionClause[];
|
|
@@ -36,6 +36,7 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
36
36
|
constructor(connection: Connection, table: string);
|
|
37
37
|
private get grammar();
|
|
38
38
|
private invalidateSqlCache;
|
|
39
|
+
private normalizeEagerLoads;
|
|
39
40
|
setModel(model: ModelConstructor): this;
|
|
40
41
|
table(table: string): this;
|
|
41
42
|
select(...columns: ModelColumn<T>[]): this;
|
|
@@ -106,7 +107,7 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
106
107
|
crossJoin(table: string): this;
|
|
107
108
|
union(query: Builder<T> | string, all?: boolean): this;
|
|
108
109
|
unionAll(query: Builder<T> | string): this;
|
|
109
|
-
with(...relations:
|
|
110
|
+
with(...relations: (EagerLoadInput | EagerLoadInput[])[]): this;
|
|
110
111
|
withoutGlobalScope(scope: string): this;
|
|
111
112
|
withoutGlobalScopes(): this;
|
|
112
113
|
withTrashed(): this;
|
|
@@ -32,6 +32,23 @@ export class Builder {
|
|
|
32
32
|
invalidateSqlCache() {
|
|
33
33
|
this.sqlCache = undefined;
|
|
34
34
|
}
|
|
35
|
+
normalizeEagerLoads(relations) {
|
|
36
|
+
const normalized = [];
|
|
37
|
+
for (const relation of relations.flat()) {
|
|
38
|
+
if (typeof relation === "string") {
|
|
39
|
+
normalized.push({ name: relation });
|
|
40
|
+
}
|
|
41
|
+
else if ("name" in relation && typeof relation.name === "string") {
|
|
42
|
+
normalized.push(relation);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
for (const [name, constraint] of Object.entries(relation)) {
|
|
46
|
+
normalized.push({ name, constraint });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return normalized;
|
|
51
|
+
}
|
|
35
52
|
setModel(model) {
|
|
36
53
|
this.model = model;
|
|
37
54
|
return this;
|
|
@@ -332,7 +349,7 @@ export class Builder {
|
|
|
332
349
|
return this.union(query, true);
|
|
333
350
|
}
|
|
334
351
|
with(...relations) {
|
|
335
|
-
this.eagerLoads.push(...relations);
|
|
352
|
+
this.eagerLoads.push(...this.normalizeEagerLoads(relations));
|
|
336
353
|
return this;
|
|
337
354
|
}
|
|
338
355
|
withoutGlobalScope(scope) {
|
package/package.json
CHANGED