joist-orm 0.1.536 → 1.0.0
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/build/{BaseEntity.d.ts → src/BaseEntity.d.ts} +2 -1
- package/build/{BaseEntity.js → src/BaseEntity.js} +13 -9
- package/build/src/BaseEntity.js.map +1 -0
- package/build/{EntityManager.d.ts → src/EntityManager.d.ts} +139 -110
- package/build/{EntityManager.js → src/EntityManager.js} +281 -262
- package/build/src/EntityManager.js.map +1 -0
- package/build/{QueryBuilder.d.ts → src/QueryBuilder.d.ts} +53 -3
- package/build/src/QueryBuilder.js +341 -0
- package/build/src/QueryBuilder.js.map +1 -0
- package/build/src/Todo.d.ts +25 -0
- package/build/src/Todo.js +52 -0
- package/build/src/Todo.js.map +1 -0
- package/build/src/changes.d.ts +34 -0
- package/build/src/changes.js +37 -0
- package/build/src/changes.js.map +1 -0
- package/build/src/config.d.ts +43 -0
- package/build/src/config.js +114 -0
- package/build/src/config.js.map +1 -0
- package/build/{createOrUpdatePartial.d.ts → src/createOrUpdatePartial.d.ts} +2 -1
- package/build/{createOrUpdatePartial.js → src/createOrUpdatePartial.js} +42 -10
- package/build/src/createOrUpdatePartial.js.map +1 -0
- package/build/src/dataloaders/findDataLoader.d.ts +5 -0
- package/build/src/dataloaders/findDataLoader.js +28 -0
- package/build/src/dataloaders/findDataLoader.js.map +1 -0
- package/build/src/dataloaders/loadDataLoader.d.ts +3 -0
- package/build/src/dataloaders/loadDataLoader.js +37 -0
- package/build/src/dataloaders/loadDataLoader.js.map +1 -0
- package/build/src/dataloaders/manyToManyDataLoader.d.ts +5 -0
- package/build/src/dataloaders/manyToManyDataLoader.js +78 -0
- package/build/src/dataloaders/manyToManyDataLoader.js.map +1 -0
- package/build/src/dataloaders/manyToManyFindDataLoader.d.ts +5 -0
- package/build/src/dataloaders/manyToManyFindDataLoader.js +33 -0
- package/build/src/dataloaders/manyToManyFindDataLoader.js.map +1 -0
- package/build/src/dataloaders/oneToManyDataLoader.d.ts +4 -0
- package/build/src/dataloaders/oneToManyDataLoader.js +40 -0
- package/build/src/dataloaders/oneToManyDataLoader.js.map +1 -0
- package/build/src/dataloaders/oneToManyFindDataLoader.d.ts +5 -0
- package/build/src/dataloaders/oneToManyFindDataLoader.js +32 -0
- package/build/src/dataloaders/oneToManyFindDataLoader.js.map +1 -0
- package/build/src/dataloaders/oneToOneDataLoader.d.ts +4 -0
- package/build/src/dataloaders/oneToOneDataLoader.js +40 -0
- package/build/src/dataloaders/oneToOneDataLoader.js.map +1 -0
- package/build/src/drivers/IdAssigner.d.ts +33 -0
- package/build/src/drivers/IdAssigner.js +106 -0
- package/build/src/drivers/IdAssigner.js.map +1 -0
- package/build/src/drivers/InMemoryDriver.d.ts +29 -0
- package/build/src/drivers/InMemoryDriver.js +306 -0
- package/build/src/drivers/InMemoryDriver.js.map +1 -0
- package/build/src/drivers/PostgresDriver.d.ts +40 -0
- package/build/src/drivers/PostgresDriver.js +376 -0
- package/build/src/drivers/PostgresDriver.js.map +1 -0
- package/build/src/drivers/driver.d.ts +23 -0
- package/build/src/drivers/driver.js +3 -0
- package/build/src/drivers/driver.js.map +1 -0
- package/build/src/drivers/index.d.ts +4 -0
- package/build/src/drivers/index.js +17 -0
- package/build/src/drivers/index.js.map +1 -0
- package/build/{getProperties.d.ts → src/getProperties.d.ts} +0 -0
- package/build/{getProperties.js → src/getProperties.js} +1 -1
- package/build/src/getProperties.js.map +1 -0
- package/build/src/index.d.ts +62 -0
- package/build/src/index.js +263 -0
- package/build/src/index.js.map +1 -0
- package/build/src/keys.d.ts +30 -0
- package/build/{keys.js → src/keys.js} +48 -16
- package/build/src/keys.js.map +1 -0
- package/build/{loadLens.d.ts → src/loadLens.d.ts} +2 -2
- package/build/{loadLens.js → src/loadLens.js} +1 -1
- package/build/src/loadLens.js.map +1 -0
- package/build/src/loaded.d.ts +49 -0
- package/build/src/loaded.js +9 -0
- package/build/src/loaded.js.map +1 -0
- package/build/{newTestInstance.d.ts → src/newTestInstance.d.ts} +37 -3
- package/build/src/newTestInstance.js +342 -0
- package/build/src/newTestInstance.js.map +1 -0
- package/build/{collections → src/relations}/AbstractRelationImpl.d.ts +6 -5
- package/build/{collections → src/relations}/AbstractRelationImpl.js +0 -0
- package/build/src/relations/AbstractRelationImpl.js.map +1 -0
- package/build/src/relations/Collection.d.ts +26 -0
- package/build/src/relations/Collection.js +19 -0
- package/build/src/relations/Collection.js.map +1 -0
- package/build/{collections → src/relations}/CustomCollection.d.ts +6 -2
- package/build/{collections → src/relations}/CustomCollection.js +17 -9
- package/build/src/relations/CustomCollection.js.map +1 -0
- package/build/{collections → src/relations}/CustomReference.d.ts +7 -2
- package/build/{collections → src/relations}/CustomReference.js +16 -9
- package/build/src/relations/CustomReference.js.map +1 -0
- package/build/src/relations/LargeCollection.d.ts +17 -0
- package/build/src/relations/LargeCollection.js +3 -0
- package/build/src/relations/LargeCollection.js.map +1 -0
- package/build/{collections → src/relations}/ManyToManyCollection.d.ts +9 -2
- package/build/src/relations/ManyToManyCollection.js +249 -0
- package/build/src/relations/ManyToManyCollection.js.map +1 -0
- package/build/src/relations/ManyToManyLargeCollection.d.ts +25 -0
- package/build/src/relations/ManyToManyLargeCollection.js +97 -0
- package/build/src/relations/ManyToManyLargeCollection.js.map +1 -0
- package/build/src/relations/ManyToOneReference.d.ts +77 -0
- package/build/{collections → src/relations}/ManyToOneReference.js +101 -48
- package/build/src/relations/ManyToOneReference.js.map +1 -0
- package/build/{collections → src/relations}/OneToManyCollection.d.ts +10 -2
- package/build/{collections → src/relations}/OneToManyCollection.js +54 -59
- package/build/src/relations/OneToManyCollection.js.map +1 -0
- package/build/src/relations/OneToManyLargeCollection.d.ts +25 -0
- package/build/src/relations/OneToManyLargeCollection.js +83 -0
- package/build/src/relations/OneToManyLargeCollection.js.map +1 -0
- package/build/src/relations/OneToOneReference.d.ts +82 -0
- package/build/src/relations/OneToOneReference.js +168 -0
- package/build/src/relations/OneToOneReference.js.map +1 -0
- package/build/src/relations/PolymorphicReference.d.ts +69 -0
- package/build/src/relations/PolymorphicReference.js +210 -0
- package/build/src/relations/PolymorphicReference.js.map +1 -0
- package/build/src/relations/Reference.d.ts +29 -0
- package/build/src/relations/Reference.js +23 -0
- package/build/src/relations/Reference.js.map +1 -0
- package/build/src/relations/Relation.d.ts +10 -0
- package/build/src/relations/Relation.js +13 -0
- package/build/src/relations/Relation.js.map +1 -0
- package/build/src/relations/hasAsyncProperty.d.ts +36 -0
- package/build/src/relations/hasAsyncProperty.js +55 -0
- package/build/src/relations/hasAsyncProperty.js.map +1 -0
- package/build/{collections → src/relations}/hasManyDerived.d.ts +2 -1
- package/build/{collections → src/relations}/hasManyDerived.js +1 -1
- package/build/src/relations/hasManyDerived.js.map +1 -0
- package/build/{collections → src/relations}/hasManyThrough.d.ts +0 -0
- package/build/{collections → src/relations}/hasManyThrough.js +2 -2
- package/build/src/relations/hasManyThrough.js.map +1 -0
- package/build/{collections → src/relations}/hasOneDerived.d.ts +3 -2
- package/build/{collections → src/relations}/hasOneDerived.js +1 -1
- package/build/src/relations/hasOneDerived.js.map +1 -0
- package/build/{collections → src/relations}/hasOneThrough.d.ts +0 -0
- package/build/{collections → src/relations}/hasOneThrough.js +2 -2
- package/build/src/relations/hasOneThrough.js.map +1 -0
- package/build/src/relations/index.d.ts +18 -0
- package/build/src/relations/index.js +53 -0
- package/build/src/relations/index.js.map +1 -0
- package/build/{reverseHint.d.ts → src/reverseHint.d.ts} +2 -1
- package/build/{reverseHint.js → src/reverseHint.js} +13 -9
- package/build/src/reverseHint.js.map +1 -0
- package/build/src/rules.d.ts +23 -0
- package/build/src/rules.js +23 -0
- package/build/src/rules.js.map +1 -0
- package/build/src/serde.d.ts +121 -0
- package/build/src/serde.js +190 -0
- package/build/src/serde.js.map +1 -0
- package/build/{utils.d.ts → src/utils.d.ts} +2 -0
- package/build/{utils.js → src/utils.js} +10 -1
- package/build/src/utils.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +30 -15
- package/build/BaseEntity.js.map +0 -1
- package/build/EntityManager.js.map +0 -1
- package/build/EntityPersister.d.ts +0 -30
- package/build/EntityPersister.js +0 -197
- package/build/EntityPersister.js.map +0 -1
- package/build/QueryBuilder.js +0 -195
- package/build/QueryBuilder.js.map +0 -1
- package/build/changes.d.ts +0 -23
- package/build/changes.js +0 -14
- package/build/changes.js.map +0 -1
- package/build/collections/AbstractRelationImpl.js.map +0 -1
- package/build/collections/CustomCollection.js.map +0 -1
- package/build/collections/CustomReference.js.map +0 -1
- package/build/collections/ManyToManyCollection.js +0 -288
- package/build/collections/ManyToManyCollection.js.map +0 -1
- package/build/collections/ManyToOneReference.d.ts +0 -50
- package/build/collections/ManyToOneReference.js.map +0 -1
- package/build/collections/OneToManyCollection.js.map +0 -1
- package/build/collections/OneToOneReference.d.ts +0 -51
- package/build/collections/OneToOneReference.js +0 -132
- package/build/collections/OneToOneReference.js.map +0 -1
- package/build/collections/hasManyDerived.js.map +0 -1
- package/build/collections/hasManyThrough.js.map +0 -1
- package/build/collections/hasOneDerived.js.map +0 -1
- package/build/collections/hasOneThrough.js.map +0 -1
- package/build/collections/index.d.ts +0 -19
- package/build/collections/index.js +0 -49
- package/build/collections/index.js.map +0 -1
- package/build/createOrUpdatePartial.js.map +0 -1
- package/build/getProperties.js.map +0 -1
- package/build/index.d.ts +0 -140
- package/build/index.js +0 -278
- package/build/index.js.map +0 -1
- package/build/keys.d.ts +0 -21
- package/build/keys.js.map +0 -1
- package/build/loadLens.js.map +0 -1
- package/build/newTestInstance.js +0 -153
- package/build/newTestInstance.js.map +0 -1
- package/build/reverseHint.js.map +0 -1
- package/build/serde.d.ts +0 -47
- package/build/serde.js +0 -93
- package/build/serde.js.map +0 -1
- package/build/utils.js.map +0 -1
- package/package.json.bak +0 -27
- package/src/BaseEntity.ts +0 -104
- package/src/EntityManager.ts +0 -1263
- package/src/EntityPersister.ts +0 -240
- package/src/QueryBuilder.ts +0 -289
- package/src/changes.ts +0 -40
- package/src/collections/AbstractRelationImpl.ts +0 -28
- package/src/collections/CustomCollection.ts +0 -152
- package/src/collections/CustomReference.ts +0 -138
- package/src/collections/ManyToManyCollection.ts +0 -346
- package/src/collections/ManyToOneReference.ts +0 -215
- package/src/collections/OneToManyCollection.ts +0 -254
- package/src/collections/OneToOneReference.ts +0 -153
- package/src/collections/hasManyDerived.ts +0 -29
- package/src/collections/hasManyThrough.ts +0 -20
- package/src/collections/hasOneDerived.ts +0 -26
- package/src/collections/hasOneThrough.ts +0 -20
- package/src/collections/index.ts +0 -74
- package/src/createOrUpdatePartial.ts +0 -144
- package/src/getProperties.ts +0 -27
- package/src/index.ts +0 -400
- package/src/keys.ts +0 -75
- package/src/loadLens.ts +0 -126
- package/src/newTestInstance.ts +0 -205
- package/src/reverseHint.ts +0 -43
- package/src/serde.ts +0 -97
- package/src/utils.ts +0 -63
- package/tsconfig.json +0 -21
- package/tsconfig.tsbuildinfo +0 -2646
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import { Entity, EntityMetadata, IdOf, isEntity } from "../EntityManager";
|
|
2
|
-
import {
|
|
3
|
-
deTagIds,
|
|
4
|
-
ensureNotDeleted,
|
|
5
|
-
fail,
|
|
6
|
-
getEm,
|
|
7
|
-
maybeResolveReferenceToId,
|
|
8
|
-
OneToOneReference,
|
|
9
|
-
Reference,
|
|
10
|
-
setField,
|
|
11
|
-
} from "../index";
|
|
12
|
-
import { AbstractRelationImpl } from "./AbstractRelationImpl";
|
|
13
|
-
import { OneToManyCollection } from "./OneToManyCollection";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Manages a foreign key from one entity to another, i.e. `Book.author --> Author`.
|
|
17
|
-
*
|
|
18
|
-
* We keep the current `author` / `author_id` value in the `__orm.data` hash, where the
|
|
19
|
-
* current value could be either the (string) author id from the database, or an entity
|
|
20
|
-
* `Author` that the user has set.
|
|
21
|
-
*
|
|
22
|
-
* Note that if our `images.author_id` column is unique, this `ManyToOneReference` will essentially
|
|
23
|
-
* be half of a one-to-one relationship, but we'll keep using this reference on the "owning"
|
|
24
|
-
* side; the other side, i.e. `Author.image` will use a `OneToOneReference` to point back to us.
|
|
25
|
-
*/
|
|
26
|
-
export class ManyToOneReference<T extends Entity, U extends Entity, N extends never | undefined>
|
|
27
|
-
extends AbstractRelationImpl<U>
|
|
28
|
-
implements Reference<T, U, N> {
|
|
29
|
-
private loaded!: U | N;
|
|
30
|
-
// We need a separate boolean to b/c loaded == undefined can still mean "isLoaded" for nullable fks.
|
|
31
|
-
private isLoaded = false;
|
|
32
|
-
private isCascadeDelete: boolean;
|
|
33
|
-
|
|
34
|
-
constructor(
|
|
35
|
-
private entity: T,
|
|
36
|
-
public otherMeta: EntityMetadata<U>,
|
|
37
|
-
private fieldName: keyof T,
|
|
38
|
-
public otherFieldName: keyof U,
|
|
39
|
-
) {
|
|
40
|
-
super();
|
|
41
|
-
this.isCascadeDelete = otherMeta.config.__data.cascadeDeleteFields.includes(fieldName as any);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async load(opts?: { withDeleted?: boolean }): Promise<U | N> {
|
|
45
|
-
ensureNotDeleted(this.entity, { ignore: "pending" });
|
|
46
|
-
const current = this.current();
|
|
47
|
-
// Resolve the id to an entity
|
|
48
|
-
if (!isEntity(current) && current !== undefined) {
|
|
49
|
-
this.loaded = ((await getEm(this.entity).load(this.otherMeta.cstr, current)) as any) as U;
|
|
50
|
-
}
|
|
51
|
-
this.isLoaded = true;
|
|
52
|
-
return this.filterDeleted(this.loaded, opts);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
set(other: U | N): void {
|
|
56
|
-
this.setImpl(other);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
get isSet(): boolean {
|
|
60
|
-
return this.current() !== undefined;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private doGet(opts?: { withDeleted?: boolean }): U | N {
|
|
64
|
-
ensureNotDeleted(this.entity, { ignore: "pending" });
|
|
65
|
-
// This should only be callable in the type system if we've already resolved this to an instance,
|
|
66
|
-
// but, just in case we somehow got here in an unloaded state, check to see if we're already in the UoW
|
|
67
|
-
if (!this.isLoaded) {
|
|
68
|
-
const existing = this.maybeFindExisting();
|
|
69
|
-
if (existing === undefined) {
|
|
70
|
-
throw new Error(`${this.entity}.${this.fieldName} was not loaded`);
|
|
71
|
-
}
|
|
72
|
-
this.loaded = existing;
|
|
73
|
-
this.isLoaded = true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return this.filterDeleted(this.loaded, opts);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
get getWithDeleted(): U | N {
|
|
80
|
-
return this.doGet({ withDeleted: true });
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
get get(): U | N {
|
|
84
|
-
return this.doGet({ withDeleted: false });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
get id(): IdOf<U> | undefined {
|
|
88
|
-
ensureNotDeleted(this.entity, { ignore: "pending" });
|
|
89
|
-
return maybeResolveReferenceToId(this.current()) as IdOf<U> | undefined;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
get idOrFail(): IdOf<U> {
|
|
93
|
-
ensureNotDeleted(this.entity, { ignore: "pending" });
|
|
94
|
-
return this.id || fail("Reference is unset or assigned to a new entity");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
get idUntagged(): string | undefined {
|
|
98
|
-
return this.id && deTagIds(this.otherMeta, [this.id])[0];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
get idUntaggedOrFail(): string {
|
|
102
|
-
return this.idUntagged || fail("Reference is unset or assigned to a new entity");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// private impl
|
|
106
|
-
|
|
107
|
-
setFromOpts(other: U): void {
|
|
108
|
-
this.setImpl(other);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
initializeForNewEntity(): void {
|
|
112
|
-
// Our codegen'd Opts type will ensure our field is inititalized if necessary/notNull
|
|
113
|
-
this.isLoaded = true;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async refreshIfLoaded(): Promise<void> {
|
|
117
|
-
// TODO We should remember what load hints have been applied to this collection and re-apply them.
|
|
118
|
-
if (this.isLoaded) {
|
|
119
|
-
const current = this.current();
|
|
120
|
-
if (typeof current === "string") {
|
|
121
|
-
this.loaded = ((await getEm(this.entity).load(this.otherMeta.cstr, current)) as any) as U;
|
|
122
|
-
} else {
|
|
123
|
-
this.loaded = current;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
onEntityDelete(): void {
|
|
129
|
-
if (this.isCascadeDelete) {
|
|
130
|
-
const current = this.current({ withDeleted: true });
|
|
131
|
-
if (current !== undefined && typeof current !== "string") {
|
|
132
|
-
getEm(this.entity).delete(current as U);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async onEntityDeletedAndFlushing(): Promise<void> {
|
|
138
|
-
const current = await this.load({ withDeleted: true });
|
|
139
|
-
if (current !== undefined) {
|
|
140
|
-
const o2m = this.getOtherRelation(current);
|
|
141
|
-
if (o2m instanceof OneToManyCollection) {
|
|
142
|
-
o2m.remove(this.entity, { requireLoaded: false });
|
|
143
|
-
} else {
|
|
144
|
-
o2m.set(undefined as any);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
setField(this.entity, this.fieldName as string, undefined);
|
|
148
|
-
this.loaded = undefined as any;
|
|
149
|
-
this.isLoaded = true;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Internal method used by OneToManyCollection
|
|
153
|
-
setImpl(other: U | N): void {
|
|
154
|
-
if (other?.isNewEntity ? other === this.loaded : this.id === other?.id) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// we may not be loaded yet, but our previous entity might already be in the UoW
|
|
159
|
-
const previousLoaded = this.loaded ?? this.maybeFindExisting();
|
|
160
|
-
|
|
161
|
-
ensureNotDeleted(this.entity, { ignore: "pending" });
|
|
162
|
-
|
|
163
|
-
// Prefer to keep the id in our data hash, but if this is a new entity w/o an id, use the entity itself
|
|
164
|
-
const changed = setField(this.entity, this.fieldName as string, other?.id ?? other);
|
|
165
|
-
if (!changed) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
this.loaded = other;
|
|
169
|
-
this.isLoaded = true;
|
|
170
|
-
|
|
171
|
-
// If had an existing value, remove us from its collection
|
|
172
|
-
if (previousLoaded) {
|
|
173
|
-
const prevRelation = this.getOtherRelation(previousLoaded);
|
|
174
|
-
if (prevRelation instanceof OneToManyCollection) {
|
|
175
|
-
prevRelation.removeIfLoaded(this.entity);
|
|
176
|
-
} else {
|
|
177
|
-
prevRelation.set(undefined as any);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (other !== undefined) {
|
|
181
|
-
const newRelation = this.getOtherRelation(other);
|
|
182
|
-
if (newRelation instanceof OneToManyCollection) {
|
|
183
|
-
newRelation.add(this.entity);
|
|
184
|
-
} else {
|
|
185
|
-
newRelation.set(this.entity);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// We need to keep U in data[fieldName] to handle entities without an id assigned yet.
|
|
191
|
-
current(opts?: { withDeleted?: boolean }): U | string | N {
|
|
192
|
-
const current = this.entity.__orm.data[this.fieldName];
|
|
193
|
-
if (current !== undefined && isEntity(current)) {
|
|
194
|
-
return this.filterDeleted(current as U, opts);
|
|
195
|
-
}
|
|
196
|
-
return current;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
public toString(): string {
|
|
200
|
-
return `ManyToOneReference(entity: ${this.entity}, fieldName: ${this.fieldName}, otherType: ${this.otherMeta.type}, otherFieldName: ${this.otherFieldName}, id: ${this.id})`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
private filterDeleted(entity: U | N, opts?: { withDeleted?: boolean }): U | N {
|
|
204
|
-
return opts?.withDeleted === true || entity === undefined || !entity.isDeletedEntity ? entity : (undefined as N);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/** Returns the other relation that points back at us, i.e. we're `book.author_id` and this is `Author.books`. */
|
|
208
|
-
private getOtherRelation(other: U): OneToManyCollection<U, T> | OneToOneReference<U, T> {
|
|
209
|
-
return (other as U)[this.otherFieldName] as any;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
private maybeFindExisting(): U | undefined {
|
|
213
|
-
return this.id !== undefined ? getEm(this.entity)["findExistingInstance"](this.id) : undefined;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import DataLoader from "dataloader";
|
|
2
|
-
import {
|
|
3
|
-
assertIdsAreTagged,
|
|
4
|
-
Collection,
|
|
5
|
-
deTagIds,
|
|
6
|
-
ensureNotDeleted,
|
|
7
|
-
Entity,
|
|
8
|
-
EntityMetadata,
|
|
9
|
-
getEm,
|
|
10
|
-
getMetadata,
|
|
11
|
-
IdOf,
|
|
12
|
-
maybeResolveReferenceToId,
|
|
13
|
-
} from "../index";
|
|
14
|
-
import { getOrSet, groupBy, remove } from "../utils";
|
|
15
|
-
import { AbstractRelationImpl } from "./AbstractRelationImpl";
|
|
16
|
-
import { ManyToOneReference } from "./ManyToOneReference";
|
|
17
|
-
|
|
18
|
-
export class OneToManyCollection<T extends Entity, U extends Entity> extends AbstractRelationImpl<U[]>
|
|
19
|
-
implements Collection<T, U> {
|
|
20
|
-
private loaded: U[] | undefined;
|
|
21
|
-
// We don't need to track removedBeforeLoaded, because if a child is removed in our unloaded state,
|
|
22
|
-
// when we load and get back the `child X has parent_id = our id` rows from the db, `loaderForCollection`
|
|
23
|
-
// groups the hydrated rows by their _current parent m2o field value_, which for a removed child will no
|
|
24
|
-
// longer be us, so it will effectively not show up in our post-load `loaded` array.
|
|
25
|
-
private addedBeforeLoaded: U[] = [];
|
|
26
|
-
private isCascadeDelete: boolean;
|
|
27
|
-
|
|
28
|
-
constructor(
|
|
29
|
-
// These are public to our internal implementation but not exposed in the Collection API
|
|
30
|
-
public entity: T,
|
|
31
|
-
public otherMeta: EntityMetadata<U>,
|
|
32
|
-
public fieldName: keyof T,
|
|
33
|
-
public otherFieldName: keyof U,
|
|
34
|
-
public otherColumnName: string,
|
|
35
|
-
) {
|
|
36
|
-
super();
|
|
37
|
-
this.isCascadeDelete = getMetadata(entity).config.__data.cascadeDeleteFields.includes(fieldName as any);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// opts is an internal parameter
|
|
41
|
-
async load(opts?: { withDeleted?: boolean }): Promise<readonly U[]> {
|
|
42
|
-
ensureNotDeleted(this.entity, { ignore: "pending" });
|
|
43
|
-
if (this.loaded === undefined) {
|
|
44
|
-
if (this.entity.id === undefined) {
|
|
45
|
-
this.loaded = [];
|
|
46
|
-
} else {
|
|
47
|
-
this.loaded = await loaderForCollection(this).load(this.entity.id);
|
|
48
|
-
}
|
|
49
|
-
this.maybeAppendAddedBeforeLoaded();
|
|
50
|
-
}
|
|
51
|
-
return this.filterDeleted(this.loaded, opts);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async find(id: IdOf<U>): Promise<U | undefined> {
|
|
55
|
-
return (await this.load()).find((u) => u.id === id);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
get get(): U[] {
|
|
59
|
-
return this.filterDeleted(this.doGet(), { withDeleted: false });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
get getWithDeleted(): U[] {
|
|
63
|
-
return this.filterDeleted(this.doGet(), { withDeleted: true });
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private doGet(): U[] {
|
|
67
|
-
ensureNotDeleted(this.entity, { ignore: "pending" });
|
|
68
|
-
if (this.loaded === undefined) {
|
|
69
|
-
if (this.entity.id === undefined) {
|
|
70
|
-
return this.addedBeforeLoaded;
|
|
71
|
-
} else {
|
|
72
|
-
// This should only be callable in the type system if we've already resolved this to an instance
|
|
73
|
-
throw new Error("get was called when not preloaded");
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return this.loaded;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
set(values: U[]): void {
|
|
80
|
-
ensureNotDeleted(this.entity);
|
|
81
|
-
if (this.loaded === undefined) {
|
|
82
|
-
throw new Error("set was called when not loaded");
|
|
83
|
-
}
|
|
84
|
-
// Make a copy for safe iteration
|
|
85
|
-
const loaded = [...this.loaded];
|
|
86
|
-
// Remove old values
|
|
87
|
-
for (const other of loaded) {
|
|
88
|
-
if (!values.includes(other)) {
|
|
89
|
-
this.remove(other);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
for (const other of values) {
|
|
93
|
-
if (!loaded.includes(other)) {
|
|
94
|
-
this.add(other);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
add(other: U): void {
|
|
100
|
-
ensureNotDeleted(this.entity);
|
|
101
|
-
if (this.loaded === undefined) {
|
|
102
|
-
if (!this.addedBeforeLoaded.includes(other)) {
|
|
103
|
-
this.addedBeforeLoaded.push(other);
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
if (!this.loaded.includes(other)) {
|
|
107
|
-
this.loaded.push(other);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// This will no-op and mark other dirty if necessary
|
|
111
|
-
this.getOtherRelation(other).set(this.entity);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// We're not supported remove(other) because that might leave other.otherFieldName as undefined,
|
|
115
|
-
// which we don't know if that's valid or not, i.e. depending on whether the field is nullable.
|
|
116
|
-
remove(other: U, opts: { requireLoaded: boolean } = { requireLoaded: true }) {
|
|
117
|
-
ensureNotDeleted(this.entity, { ignore: "pending" });
|
|
118
|
-
if (this.loaded === undefined && opts.requireLoaded) {
|
|
119
|
-
throw new Error("remove was called when not loaded");
|
|
120
|
-
}
|
|
121
|
-
remove(this.loaded || this.addedBeforeLoaded, other);
|
|
122
|
-
// This will no-op and mark other dirty if necessary
|
|
123
|
-
this.getOtherRelation(other).set(undefined);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
removeAll(): void {
|
|
127
|
-
ensureNotDeleted(this.entity);
|
|
128
|
-
if (this.loaded === undefined) {
|
|
129
|
-
throw new Error("removeAll was called when not loaded");
|
|
130
|
-
}
|
|
131
|
-
for (const other of [...this.loaded]) {
|
|
132
|
-
this.remove(other);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// internal impl
|
|
137
|
-
|
|
138
|
-
setFromOpts(others: U[]): void {
|
|
139
|
-
this.loaded = [];
|
|
140
|
-
others.forEach((o) => this.add(o));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
initializeForNewEntity(): void {
|
|
144
|
-
// Don't overwrite any opts values
|
|
145
|
-
if (this.loaded === undefined) {
|
|
146
|
-
this.loaded = [];
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
removeIfLoaded(other: U) {
|
|
151
|
-
if (this.loaded !== undefined) {
|
|
152
|
-
remove(this.loaded, other);
|
|
153
|
-
} else {
|
|
154
|
-
remove(this.addedBeforeLoaded, other);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async refreshIfLoaded(): Promise<void> {
|
|
159
|
-
// TODO We should remember what load hints have been applied to this collection and re-apply them.
|
|
160
|
-
if (this.loaded !== undefined && this.entity.id !== undefined) {
|
|
161
|
-
const loader = loaderForCollection(this);
|
|
162
|
-
loader.clear(this.entity.id);
|
|
163
|
-
this.loaded = await loader.load(this.entity.id);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
onEntityDelete(): void {
|
|
168
|
-
if (this.isCascadeDelete) {
|
|
169
|
-
this.current({ withDeleted: true }).forEach(getEm(this.entity).delete);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// We already unhooked all children in our addedBeforeLoaded list; now load the full list if necessary.
|
|
174
|
-
async onEntityDeletedAndFlushing(): Promise<void> {
|
|
175
|
-
const current = await this.load({ withDeleted: true });
|
|
176
|
-
current.forEach((other) => {
|
|
177
|
-
const m2o = this.getOtherRelation(other);
|
|
178
|
-
if (maybeResolveReferenceToId(m2o.current()) === this.entity.id) {
|
|
179
|
-
// TODO What if other.otherFieldName is required/not-null?
|
|
180
|
-
m2o.set(undefined);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
this.loaded = [];
|
|
184
|
-
this.addedBeforeLoaded = [];
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private maybeAppendAddedBeforeLoaded(): void {
|
|
188
|
-
if (this.loaded) {
|
|
189
|
-
const newEntities = this.addedBeforeLoaded.filter((e) => !this.loaded?.includes(e));
|
|
190
|
-
// Push on the end to better match the db order of "newer things come last"
|
|
191
|
-
for (const e of newEntities) {
|
|
192
|
-
this.loaded.push(e);
|
|
193
|
-
}
|
|
194
|
-
this.addedBeforeLoaded = [];
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
current(opts?: { withDeleted?: boolean }): U[] {
|
|
199
|
-
return this.filterDeleted(this.loaded || this.addedBeforeLoaded, opts);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
public toString(): string {
|
|
203
|
-
return `OneToManyCollection(entity: ${this.entity}, fieldName: ${this.fieldName}, otherType: ${this.otherMeta.type}, otherFieldName: ${this.otherFieldName})`;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
private filterDeleted(entities: U[], opts?: { withDeleted?: boolean }): U[] {
|
|
207
|
-
return opts?.withDeleted === true ? [...entities] : entities.filter((e) => !e.isDeletedEntity);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/** Returns the other relation that points back at us, i.e. we're `Author.image` and this is `Image.author_id`. */
|
|
211
|
-
private getOtherRelation(other: U): ManyToOneReference<U, T, any> {
|
|
212
|
-
return (other as U)[this.otherFieldName] as any;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function loaderForCollection<T extends Entity, U extends Entity>(
|
|
217
|
-
collection: OneToManyCollection<T, U>,
|
|
218
|
-
): DataLoader<string, U[]> {
|
|
219
|
-
const em = getEm(collection.entity);
|
|
220
|
-
// The metadata for the entity that contains the collection
|
|
221
|
-
const meta = getMetadata(collection.entity);
|
|
222
|
-
const loaderName = `${meta.tableName}.${collection.fieldName}`;
|
|
223
|
-
return getOrSet(em.__data.loaders, loaderName, () => {
|
|
224
|
-
return new DataLoader<string, U[]>(async (_keys) => {
|
|
225
|
-
const { otherMeta } = collection;
|
|
226
|
-
|
|
227
|
-
assertIdsAreTagged(_keys);
|
|
228
|
-
const keys = deTagIds(meta, _keys);
|
|
229
|
-
|
|
230
|
-
const rows = await em.knex
|
|
231
|
-
.select("*")
|
|
232
|
-
.from(otherMeta.tableName)
|
|
233
|
-
.whereIn(collection.otherColumnName, keys)
|
|
234
|
-
.orderBy("id");
|
|
235
|
-
|
|
236
|
-
const entities = rows.map((row) => em.hydrate(otherMeta.cstr, row, { overwriteExisting: false }));
|
|
237
|
-
// .filter((e) => !e.isDeletedEntity);
|
|
238
|
-
|
|
239
|
-
const rowsById = groupBy(entities, (entity) => {
|
|
240
|
-
// TODO If this came from the UoW, it may not be an id? I.e. pre-insert.
|
|
241
|
-
const ownerId = maybeResolveReferenceToId(entity.__orm.data[collection.otherFieldName]);
|
|
242
|
-
// We almost always expect ownerId to be found, b/c normally we just hydrated this entity
|
|
243
|
-
// directly from a SQL row with owner_id=X, however we might be loading this collection
|
|
244
|
-
// (i.e. find all children where owner_id=X) when the SQL thinks a child is still pointing
|
|
245
|
-
// at the parent (i.e. owner_id=X in the db), but our already-loaded child has had its
|
|
246
|
-
// `child.owner` field either changed to some other owner, or set to undefined. In either,
|
|
247
|
-
// that child should no longer be parent of this owner's collection, so just return a
|
|
248
|
-
// dummy value.
|
|
249
|
-
return ownerId ?? "dummyNoLongerOwned";
|
|
250
|
-
});
|
|
251
|
-
return _keys.map((k) => rowsById.get(k) || []);
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
}
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { deTagIds, ensureNotDeleted, fail, getEm, IdOf, Reference, unsafeDeTagIds } from "../";
|
|
2
|
-
import { Entity, EntityMetadata, getMetadata } from "../EntityManager";
|
|
3
|
-
import { AbstractRelationImpl } from "./AbstractRelationImpl";
|
|
4
|
-
import { ManyToOneReference } from "./ManyToOneReference";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Represents the "many" side of a one-to-one relationship.
|
|
8
|
-
*
|
|
9
|
-
* I.e. in a one-to-many from Book -> Reviews, there is a review.book_id that can have many books.
|
|
10
|
-
*
|
|
11
|
-
* This class is for when that `review.book_id` column is itself unique, i.e. like `image.book_id`, and
|
|
12
|
-
* so instead of `Book.images: OneToManyCollection` we have a `Book.image: OneToOneReference`.
|
|
13
|
-
*
|
|
14
|
-
* This class implements `Reference` because it is essentially like "one entity pointing/refereing to another",
|
|
15
|
-
* however because we require a `.load` call to lazily know the value of other side (unlike ManyToOneReference
|
|
16
|
-
* which has it's `book_id` column immediately available in the entity `data` hash), there is some wonkiness
|
|
17
|
-
* around methods like `Reference.id` that are usually callable without `load`/`populate`, that for this
|
|
18
|
-
* class can actually only be called post `load`/`populate`.
|
|
19
|
-
*
|
|
20
|
-
* Currently we enforce this with a runtime check, which is not great, but the trade-off of implementing
|
|
21
|
-
* `Reference` seemed worth the downside of a un-type-safe `.id` property.
|
|
22
|
-
*/
|
|
23
|
-
export class OneToOneReference<T extends Entity, U extends Entity> extends AbstractRelationImpl<U>
|
|
24
|
-
implements Reference<T, U, undefined> {
|
|
25
|
-
private loaded: U | undefined;
|
|
26
|
-
private isLoaded: boolean = false;
|
|
27
|
-
private isCascadeDelete: boolean;
|
|
28
|
-
|
|
29
|
-
constructor(
|
|
30
|
-
// These are public to our internal implementation but not exposed in the Collection API
|
|
31
|
-
public entity: T,
|
|
32
|
-
public otherMeta: EntityMetadata<U>,
|
|
33
|
-
public fieldName: keyof T,
|
|
34
|
-
public otherFieldName: keyof U,
|
|
35
|
-
) {
|
|
36
|
-
super();
|
|
37
|
-
this.isCascadeDelete = getMetadata(entity).config.__data.cascadeDeleteFields.includes(fieldName as any);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
get id(): IdOf<U> | undefined {
|
|
41
|
-
if (this.isLoaded) {
|
|
42
|
-
return this.loaded?.id as IdOf<U> | undefined;
|
|
43
|
-
}
|
|
44
|
-
throw new Error(`${this.entity}.${this.fieldName} was not loaded`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
get idOrFail(): IdOf<U> {
|
|
48
|
-
return this.id || fail(`${this.entity}.${this.fieldName} has no id yet`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
get idUntagged(): string | undefined {
|
|
52
|
-
return this.id && deTagIds(this.otherMeta, [this.id])[0];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
get idUntaggedOrFail(): string {
|
|
56
|
-
return this.idUntagged || fail("Reference is unset or assigned to a new entity");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
get isSet(): boolean {
|
|
60
|
-
// This will failure if we're not loaded yet
|
|
61
|
-
return this.id !== undefined;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// opts is an internal parameter
|
|
65
|
-
async load(opts?: { withDeleted?: boolean }): Promise<U | undefined> {
|
|
66
|
-
ensureNotDeleted(this.entity, { ignore: "pending" });
|
|
67
|
-
if (!this.isLoaded) {
|
|
68
|
-
if (this.entity.id !== undefined) {
|
|
69
|
-
this.loaded = (
|
|
70
|
-
await getEm(this.entity).find(this.otherMeta.cstr, {
|
|
71
|
-
[this.otherFieldName]: this.entity,
|
|
72
|
-
} as any)
|
|
73
|
-
)[0];
|
|
74
|
-
}
|
|
75
|
-
// this.maybeAppendAddedBeforeLoaded();
|
|
76
|
-
this.isLoaded = true;
|
|
77
|
-
}
|
|
78
|
-
return this.filterDeleted(this.loaded, opts);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
set(other: U): void {
|
|
82
|
-
ensureNotDeleted(this.entity);
|
|
83
|
-
if (other === this.loaded) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (this.isLoaded) {
|
|
87
|
-
if (this.loaded) {
|
|
88
|
-
this.getOtherRelation(this.loaded).set(undefined);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
this.loaded = other;
|
|
92
|
-
this.isLoaded = true;
|
|
93
|
-
// This will no-op and mark other dirty if necessary
|
|
94
|
-
if (other) {
|
|
95
|
-
this.getOtherRelation(other).set(this.entity);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
get getWithDeleted(): U | undefined {
|
|
100
|
-
return this.filterDeleted(this.doGet(), { withDeleted: true });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
get get(): U | undefined {
|
|
104
|
-
return this.filterDeleted(this.doGet(), { withDeleted: false });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private doGet(): U | undefined {
|
|
108
|
-
ensureNotDeleted(this.entity, { ignore: "pending" });
|
|
109
|
-
if (!this.isLoaded) {
|
|
110
|
-
// This should only be callable in the type system if we've already resolved this to an instance
|
|
111
|
-
throw new Error("get was called when not preloaded");
|
|
112
|
-
}
|
|
113
|
-
return this.loaded;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// internal impl
|
|
117
|
-
|
|
118
|
-
setFromOpts(other: U): void {
|
|
119
|
-
this.set(other);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
initializeForNewEntity(): void {
|
|
123
|
-
this.isLoaded = true;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async refreshIfLoaded(): Promise<void> {
|
|
127
|
-
if (this.isLoaded) {
|
|
128
|
-
this.isLoaded = false;
|
|
129
|
-
await this.load();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
onEntityDelete(): void {
|
|
134
|
-
// if (this.isCascadeDelete) {
|
|
135
|
-
// this.current({ withDeleted: true }).forEach(getEm(this.entity).delete);
|
|
136
|
-
// }
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async onEntityDeletedAndFlushing(): Promise<void> {}
|
|
140
|
-
|
|
141
|
-
public toString(): string {
|
|
142
|
-
return `OneToOneReference(entity: ${this.entity}, fieldName: ${this.fieldName}, otherType: ${this.otherMeta.type}, otherFieldName: ${this.otherFieldName})`;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private filterDeleted(entity: U | undefined, opts?: { withDeleted?: boolean }): U | undefined {
|
|
146
|
-
return opts?.withDeleted === true || entity === undefined || !entity.isDeletedEntity ? entity : undefined;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** Returns the other relation that points back at us, i.e. we're `Author.image` and this is `Image.author_id`. */
|
|
150
|
-
private getOtherRelation(other: U): ManyToOneReference<U, T, any> {
|
|
151
|
-
return (other as U)[this.otherFieldName] as any;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { currentlyInstantiatingEntity, getEm, Collection } from "../";
|
|
2
|
-
import { Entity, Loaded, LoadHint } from "../EntityManager";
|
|
3
|
-
import { CustomCollection } from "./CustomCollection";
|
|
4
|
-
|
|
5
|
-
type HasManyDerivedOpts<T extends Entity, U extends Entity, H extends LoadHint<T>> = {
|
|
6
|
-
load?: (entity: T) => Promise<any>;
|
|
7
|
-
get: (entity: Loaded<T, H>) => readonly U[];
|
|
8
|
-
set?: (entity: Loaded<T, H>, values: U[]) => void;
|
|
9
|
-
add?: (entity: Loaded<T, H>, value: U) => void;
|
|
10
|
-
remove?: (entity: Loaded<T, H>, value: U) => void;
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Creates a CustomCollection that can conditionally walk across references in the object graph.
|
|
14
|
-
*
|
|
15
|
-
* I.e. An Author "has many reviews" through the `author -> books -> reviews` relation.
|
|
16
|
-
*
|
|
17
|
-
* Because this is based on `CustomCollection`, it will work in populates, i.e. `em.populate(author, "reviews")`.
|
|
18
|
-
*/
|
|
19
|
-
export function hasManyDerived<T extends Entity, U extends Entity, H extends LoadHint<T>>(
|
|
20
|
-
loadHint: H,
|
|
21
|
-
opts: HasManyDerivedOpts<T, U, H>,
|
|
22
|
-
): Collection<T, U> {
|
|
23
|
-
const entity: T = currentlyInstantiatingEntity as T;
|
|
24
|
-
const { load, ...rest } = opts;
|
|
25
|
-
return new CustomCollection<T, U>(entity, {
|
|
26
|
-
load: load ?? ((entity) => getEm(entity).populate(entity, loadHint)),
|
|
27
|
-
...(rest as any),
|
|
28
|
-
});
|
|
29
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Collection, CustomCollection, currentlyInstantiatingEntity, Entity, getLens, Lens, loadLens } from "../index";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates a CustomCollection that will walk across references in the object graph.
|
|
5
|
-
*
|
|
6
|
-
* I.e. An Author "has many reviews" through the `author -> books -> reviews` relation.
|
|
7
|
-
*
|
|
8
|
-
* Because this is based on `CustomCollection`, it will work in populates, i.e. `em.populate(author, "reviews")`.
|
|
9
|
-
*/
|
|
10
|
-
export function hasManyThrough<T extends Entity, U extends Entity>(
|
|
11
|
-
lens: (lens: Lens<T>) => Lens<U, U[]>,
|
|
12
|
-
): Collection<T, U> {
|
|
13
|
-
const entity: T = currentlyInstantiatingEntity as T;
|
|
14
|
-
return new CustomCollection<T, U>(entity, {
|
|
15
|
-
load: async (entity) => {
|
|
16
|
-
await loadLens(entity, lens);
|
|
17
|
-
},
|
|
18
|
-
get: () => getLens(entity, lens),
|
|
19
|
-
});
|
|
20
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { currentlyInstantiatingEntity, getEm, Reference } from "../";
|
|
2
|
-
import { Entity, Loaded, LoadHint } from "../EntityManager";
|
|
3
|
-
import { CustomReference } from "./CustomReference";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Creates a CustomReference that can conditionally walk across references in the object graph.
|
|
7
|
-
*
|
|
8
|
-
* I.e. A BookReview "has one author" through the `review -> book -> author` relation.
|
|
9
|
-
*
|
|
10
|
-
* Because this is based on `CustomReference`, it will work in populates, i.e. `em.populate(review, "author")`.
|
|
11
|
-
*/
|
|
12
|
-
export function hasOneDerived<
|
|
13
|
-
T extends Entity,
|
|
14
|
-
U extends Entity,
|
|
15
|
-
N extends never | undefined,
|
|
16
|
-
V extends U | N,
|
|
17
|
-
H extends LoadHint<T>
|
|
18
|
-
>(loadHint: H, get: (entity: Loaded<T, H>) => V): Reference<T, U, N> {
|
|
19
|
-
const entity: T = currentlyInstantiatingEntity as T;
|
|
20
|
-
return new CustomReference<T, U, N>(entity, {
|
|
21
|
-
load: async (entity) => {
|
|
22
|
-
await getEm(entity).populate(entity, loadHint);
|
|
23
|
-
},
|
|
24
|
-
get: () => get(entity as Loaded<T, H>),
|
|
25
|
-
});
|
|
26
|
-
}
|