joist-orm 0.1.538 → 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.
Files changed (221) hide show
  1. package/build/{BaseEntity.d.ts → src/BaseEntity.d.ts} +2 -1
  2. package/build/{BaseEntity.js → src/BaseEntity.js} +13 -9
  3. package/build/src/BaseEntity.js.map +1 -0
  4. package/build/{EntityManager.d.ts → src/EntityManager.d.ts} +139 -110
  5. package/build/{EntityManager.js → src/EntityManager.js} +281 -262
  6. package/build/src/EntityManager.js.map +1 -0
  7. package/build/{QueryBuilder.d.ts → src/QueryBuilder.d.ts} +53 -3
  8. package/build/src/QueryBuilder.js +341 -0
  9. package/build/src/QueryBuilder.js.map +1 -0
  10. package/build/src/Todo.d.ts +25 -0
  11. package/build/src/Todo.js +52 -0
  12. package/build/src/Todo.js.map +1 -0
  13. package/build/src/changes.d.ts +34 -0
  14. package/build/src/changes.js +37 -0
  15. package/build/src/changes.js.map +1 -0
  16. package/build/src/config.d.ts +43 -0
  17. package/build/src/config.js +114 -0
  18. package/build/src/config.js.map +1 -0
  19. package/build/{createOrUpdatePartial.d.ts → src/createOrUpdatePartial.d.ts} +2 -1
  20. package/build/{createOrUpdatePartial.js → src/createOrUpdatePartial.js} +42 -10
  21. package/build/src/createOrUpdatePartial.js.map +1 -0
  22. package/build/src/dataloaders/findDataLoader.d.ts +5 -0
  23. package/build/src/dataloaders/findDataLoader.js +28 -0
  24. package/build/src/dataloaders/findDataLoader.js.map +1 -0
  25. package/build/src/dataloaders/loadDataLoader.d.ts +3 -0
  26. package/build/src/dataloaders/loadDataLoader.js +37 -0
  27. package/build/src/dataloaders/loadDataLoader.js.map +1 -0
  28. package/build/src/dataloaders/manyToManyDataLoader.d.ts +5 -0
  29. package/build/src/dataloaders/manyToManyDataLoader.js +78 -0
  30. package/build/src/dataloaders/manyToManyDataLoader.js.map +1 -0
  31. package/build/src/dataloaders/manyToManyFindDataLoader.d.ts +5 -0
  32. package/build/src/dataloaders/manyToManyFindDataLoader.js +33 -0
  33. package/build/src/dataloaders/manyToManyFindDataLoader.js.map +1 -0
  34. package/build/src/dataloaders/oneToManyDataLoader.d.ts +4 -0
  35. package/build/src/dataloaders/oneToManyDataLoader.js +40 -0
  36. package/build/src/dataloaders/oneToManyDataLoader.js.map +1 -0
  37. package/build/src/dataloaders/oneToManyFindDataLoader.d.ts +5 -0
  38. package/build/src/dataloaders/oneToManyFindDataLoader.js +32 -0
  39. package/build/src/dataloaders/oneToManyFindDataLoader.js.map +1 -0
  40. package/build/src/dataloaders/oneToOneDataLoader.d.ts +4 -0
  41. package/build/src/dataloaders/oneToOneDataLoader.js +40 -0
  42. package/build/src/dataloaders/oneToOneDataLoader.js.map +1 -0
  43. package/build/src/drivers/IdAssigner.d.ts +33 -0
  44. package/build/src/drivers/IdAssigner.js +106 -0
  45. package/build/src/drivers/IdAssigner.js.map +1 -0
  46. package/build/src/drivers/InMemoryDriver.d.ts +29 -0
  47. package/build/src/drivers/InMemoryDriver.js +306 -0
  48. package/build/src/drivers/InMemoryDriver.js.map +1 -0
  49. package/build/src/drivers/PostgresDriver.d.ts +40 -0
  50. package/build/src/drivers/PostgresDriver.js +376 -0
  51. package/build/src/drivers/PostgresDriver.js.map +1 -0
  52. package/build/src/drivers/driver.d.ts +23 -0
  53. package/build/src/drivers/driver.js +3 -0
  54. package/build/src/drivers/driver.js.map +1 -0
  55. package/build/src/drivers/index.d.ts +4 -0
  56. package/build/src/drivers/index.js +17 -0
  57. package/build/src/drivers/index.js.map +1 -0
  58. package/build/{getProperties.d.ts → src/getProperties.d.ts} +0 -0
  59. package/build/{getProperties.js → src/getProperties.js} +1 -1
  60. package/build/src/getProperties.js.map +1 -0
  61. package/build/src/index.d.ts +62 -0
  62. package/build/src/index.js +263 -0
  63. package/build/src/index.js.map +1 -0
  64. package/build/src/keys.d.ts +30 -0
  65. package/build/{keys.js → src/keys.js} +48 -16
  66. package/build/src/keys.js.map +1 -0
  67. package/build/{loadLens.d.ts → src/loadLens.d.ts} +2 -2
  68. package/build/{loadLens.js → src/loadLens.js} +1 -1
  69. package/build/src/loadLens.js.map +1 -0
  70. package/build/src/loaded.d.ts +49 -0
  71. package/build/src/loaded.js +9 -0
  72. package/build/src/loaded.js.map +1 -0
  73. package/build/{newTestInstance.d.ts → src/newTestInstance.d.ts} +37 -3
  74. package/build/src/newTestInstance.js +342 -0
  75. package/build/src/newTestInstance.js.map +1 -0
  76. package/build/{collections → src/relations}/AbstractRelationImpl.d.ts +6 -5
  77. package/build/{collections → src/relations}/AbstractRelationImpl.js +0 -0
  78. package/build/src/relations/AbstractRelationImpl.js.map +1 -0
  79. package/build/src/relations/Collection.d.ts +26 -0
  80. package/build/src/relations/Collection.js +19 -0
  81. package/build/src/relations/Collection.js.map +1 -0
  82. package/build/{collections → src/relations}/CustomCollection.d.ts +6 -2
  83. package/build/{collections → src/relations}/CustomCollection.js +17 -9
  84. package/build/src/relations/CustomCollection.js.map +1 -0
  85. package/build/{collections → src/relations}/CustomReference.d.ts +7 -2
  86. package/build/{collections → src/relations}/CustomReference.js +16 -9
  87. package/build/src/relations/CustomReference.js.map +1 -0
  88. package/build/src/relations/LargeCollection.d.ts +17 -0
  89. package/build/src/relations/LargeCollection.js +3 -0
  90. package/build/src/relations/LargeCollection.js.map +1 -0
  91. package/build/{collections → src/relations}/ManyToManyCollection.d.ts +9 -2
  92. package/build/src/relations/ManyToManyCollection.js +249 -0
  93. package/build/src/relations/ManyToManyCollection.js.map +1 -0
  94. package/build/src/relations/ManyToManyLargeCollection.d.ts +25 -0
  95. package/build/src/relations/ManyToManyLargeCollection.js +97 -0
  96. package/build/src/relations/ManyToManyLargeCollection.js.map +1 -0
  97. package/build/src/relations/ManyToOneReference.d.ts +77 -0
  98. package/build/{collections → src/relations}/ManyToOneReference.js +101 -48
  99. package/build/src/relations/ManyToOneReference.js.map +1 -0
  100. package/build/{collections → src/relations}/OneToManyCollection.d.ts +10 -2
  101. package/build/{collections → src/relations}/OneToManyCollection.js +54 -59
  102. package/build/src/relations/OneToManyCollection.js.map +1 -0
  103. package/build/src/relations/OneToManyLargeCollection.d.ts +25 -0
  104. package/build/src/relations/OneToManyLargeCollection.js +83 -0
  105. package/build/src/relations/OneToManyLargeCollection.js.map +1 -0
  106. package/build/src/relations/OneToOneReference.d.ts +82 -0
  107. package/build/src/relations/OneToOneReference.js +168 -0
  108. package/build/src/relations/OneToOneReference.js.map +1 -0
  109. package/build/src/relations/PolymorphicReference.d.ts +69 -0
  110. package/build/src/relations/PolymorphicReference.js +210 -0
  111. package/build/src/relations/PolymorphicReference.js.map +1 -0
  112. package/build/src/relations/Reference.d.ts +29 -0
  113. package/build/src/relations/Reference.js +23 -0
  114. package/build/src/relations/Reference.js.map +1 -0
  115. package/build/src/relations/Relation.d.ts +10 -0
  116. package/build/src/relations/Relation.js +13 -0
  117. package/build/src/relations/Relation.js.map +1 -0
  118. package/build/src/relations/hasAsyncProperty.d.ts +36 -0
  119. package/build/src/relations/hasAsyncProperty.js +55 -0
  120. package/build/src/relations/hasAsyncProperty.js.map +1 -0
  121. package/build/{collections → src/relations}/hasManyDerived.d.ts +2 -1
  122. package/build/{collections → src/relations}/hasManyDerived.js +1 -1
  123. package/build/src/relations/hasManyDerived.js.map +1 -0
  124. package/build/{collections → src/relations}/hasManyThrough.d.ts +0 -0
  125. package/build/{collections → src/relations}/hasManyThrough.js +2 -2
  126. package/build/src/relations/hasManyThrough.js.map +1 -0
  127. package/build/{collections → src/relations}/hasOneDerived.d.ts +3 -2
  128. package/build/{collections → src/relations}/hasOneDerived.js +1 -1
  129. package/build/src/relations/hasOneDerived.js.map +1 -0
  130. package/build/{collections → src/relations}/hasOneThrough.d.ts +0 -0
  131. package/build/{collections → src/relations}/hasOneThrough.js +2 -2
  132. package/build/src/relations/hasOneThrough.js.map +1 -0
  133. package/build/src/relations/index.d.ts +18 -0
  134. package/build/src/relations/index.js +53 -0
  135. package/build/src/relations/index.js.map +1 -0
  136. package/build/{reverseHint.d.ts → src/reverseHint.d.ts} +2 -1
  137. package/build/{reverseHint.js → src/reverseHint.js} +13 -9
  138. package/build/src/reverseHint.js.map +1 -0
  139. package/build/src/rules.d.ts +23 -0
  140. package/build/src/rules.js +23 -0
  141. package/build/src/rules.js.map +1 -0
  142. package/build/src/serde.d.ts +121 -0
  143. package/build/src/serde.js +190 -0
  144. package/build/src/serde.js.map +1 -0
  145. package/build/{utils.d.ts → src/utils.d.ts} +2 -0
  146. package/build/{utils.js → src/utils.js} +10 -1
  147. package/build/src/utils.js.map +1 -0
  148. package/build/tsconfig.tsbuildinfo +1 -0
  149. package/package.json +30 -15
  150. package/build/BaseEntity.js.map +0 -1
  151. package/build/EntityManager.js.map +0 -1
  152. package/build/EntityPersister.d.ts +0 -30
  153. package/build/EntityPersister.js +0 -197
  154. package/build/EntityPersister.js.map +0 -1
  155. package/build/QueryBuilder.js +0 -195
  156. package/build/QueryBuilder.js.map +0 -1
  157. package/build/changes.d.ts +0 -23
  158. package/build/changes.js +0 -14
  159. package/build/changes.js.map +0 -1
  160. package/build/collections/AbstractRelationImpl.js.map +0 -1
  161. package/build/collections/CustomCollection.js.map +0 -1
  162. package/build/collections/CustomReference.js.map +0 -1
  163. package/build/collections/ManyToManyCollection.js +0 -288
  164. package/build/collections/ManyToManyCollection.js.map +0 -1
  165. package/build/collections/ManyToOneReference.d.ts +0 -50
  166. package/build/collections/ManyToOneReference.js.map +0 -1
  167. package/build/collections/OneToManyCollection.js.map +0 -1
  168. package/build/collections/OneToOneReference.d.ts +0 -51
  169. package/build/collections/OneToOneReference.js +0 -132
  170. package/build/collections/OneToOneReference.js.map +0 -1
  171. package/build/collections/hasManyDerived.js.map +0 -1
  172. package/build/collections/hasManyThrough.js.map +0 -1
  173. package/build/collections/hasOneDerived.js.map +0 -1
  174. package/build/collections/hasOneThrough.js.map +0 -1
  175. package/build/collections/index.d.ts +0 -19
  176. package/build/collections/index.js +0 -49
  177. package/build/collections/index.js.map +0 -1
  178. package/build/createOrUpdatePartial.js.map +0 -1
  179. package/build/getProperties.js.map +0 -1
  180. package/build/index.d.ts +0 -140
  181. package/build/index.js +0 -278
  182. package/build/index.js.map +0 -1
  183. package/build/keys.d.ts +0 -21
  184. package/build/keys.js.map +0 -1
  185. package/build/loadLens.js.map +0 -1
  186. package/build/newTestInstance.js +0 -153
  187. package/build/newTestInstance.js.map +0 -1
  188. package/build/reverseHint.js.map +0 -1
  189. package/build/serde.d.ts +0 -47
  190. package/build/serde.js +0 -93
  191. package/build/serde.js.map +0 -1
  192. package/build/utils.js.map +0 -1
  193. package/package.json.bak +0 -27
  194. package/src/BaseEntity.ts +0 -104
  195. package/src/EntityManager.ts +0 -1263
  196. package/src/EntityPersister.ts +0 -240
  197. package/src/QueryBuilder.ts +0 -289
  198. package/src/changes.ts +0 -40
  199. package/src/collections/AbstractRelationImpl.ts +0 -28
  200. package/src/collections/CustomCollection.ts +0 -152
  201. package/src/collections/CustomReference.ts +0 -138
  202. package/src/collections/ManyToManyCollection.ts +0 -346
  203. package/src/collections/ManyToOneReference.ts +0 -215
  204. package/src/collections/OneToManyCollection.ts +0 -254
  205. package/src/collections/OneToOneReference.ts +0 -153
  206. package/src/collections/hasManyDerived.ts +0 -29
  207. package/src/collections/hasManyThrough.ts +0 -20
  208. package/src/collections/hasOneDerived.ts +0 -26
  209. package/src/collections/hasOneThrough.ts +0 -20
  210. package/src/collections/index.ts +0 -74
  211. package/src/createOrUpdatePartial.ts +0 -144
  212. package/src/getProperties.ts +0 -27
  213. package/src/index.ts +0 -400
  214. package/src/keys.ts +0 -75
  215. package/src/loadLens.ts +0 -126
  216. package/src/newTestInstance.ts +0 -205
  217. package/src/reverseHint.ts +0 -43
  218. package/src/serde.ts +0 -97
  219. package/src/utils.ts +0 -63
  220. package/tsconfig.json +0 -21
  221. 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
- }