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.
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,240 +0,0 @@
1
- import Knex from "knex";
2
- import { JoinRow, ManyToManyCollection } from "./collections/ManyToManyCollection";
3
- import {
4
- deTagIds,
5
- Entity,
6
- EntityMetadata,
7
- getMetadata,
8
- keyToNumber,
9
- keyToString,
10
- maybeResolveReferenceToId,
11
- } from "./index";
12
- import { partition } from "./utils";
13
-
14
- /** The operations for a given entity type, so they can be executed in bulk. */
15
- export interface Todo {
16
- metadata: EntityMetadata<any>;
17
- inserts: Entity[];
18
- updates: Entity[];
19
- deletes: Entity[];
20
- validates: Entity[];
21
- }
22
-
23
- export async function flushEntities(knex: Knex, todos: Record<string, Todo>): Promise<void> {
24
- const updatedAt = new Date();
25
- await assignNewIds(knex, todos);
26
- for await (const todo of Object.values(todos)) {
27
- if (todo) {
28
- const meta = todo.metadata;
29
- if (todo.inserts.length > 0) {
30
- await batchInsert(knex, meta, todo.inserts);
31
- }
32
- if (todo.updates.length > 0) {
33
- todo.updates.forEach((e) => (e.__orm.data["updatedAt"] = updatedAt));
34
- await batchUpdate(knex, meta, todo.updates);
35
- }
36
- if (todo.deletes.length > 0) {
37
- await batchDelete(knex, meta, todo.deletes);
38
- }
39
- }
40
- }
41
- }
42
-
43
- /**
44
- * Assigns all new entities an id directly from their corresponding sequence generator, instead of via INSERTs.
45
- *
46
- * This lets us avoid cyclic issues with some INSERTs having foreign keys to other rows that themselves
47
- * need to first be INSERTed.
48
- */
49
- async function assignNewIds(knex: Knex, todos: Record<string, Todo>): Promise<void> {
50
- const seqStatements: string[] = [];
51
- Object.values(todos).forEach((todo) => {
52
- if (todo.inserts.length > 0) {
53
- const meta = todo.inserts[0].__orm.metadata;
54
- const sequenceName = `${meta.tableName}_id_seq`;
55
- const sql = `select nextval('${sequenceName}') from generate_series(1, ${todo.inserts.length})`;
56
- seqStatements.push(sql);
57
- }
58
- });
59
- if (seqStatements.length > 0) {
60
- // There will be 1 per table; 1 single insert should be fine but we might need to batch for super-large schemas?
61
- const sql = seqStatements.join(" UNION ALL ");
62
- const result = await knex.raw(sql);
63
- let i = 0;
64
- Object.values(todos).forEach((todo) => {
65
- for (const insert of todo.inserts) {
66
- insert.__orm.data["id"] = keyToString(todo.metadata, result.rows![i++]["nextval"]);
67
- }
68
- });
69
- }
70
- }
71
-
72
- async function batchInsert(knex: Knex, meta: EntityMetadata<any>, entities: Entity[]): Promise<void> {
73
- const rows = entities.map((entity) => {
74
- const row = {};
75
- meta.columns.forEach((c) => c.serde.setOnRow(entity.__orm.data, row));
76
- return row;
77
- });
78
- // We don't use the ids that come back from batchInsert b/c we pre-assign ids for both inserts and updates.
79
- // We also use `.transacting` b/c even when `knex` is already a Transaction object,
80
- // `batchInsert` w/o the `transacting` adds savepoints that we don't want/need.
81
- await knex.batchInsert(meta.tableName, rows).transacting(knex as any);
82
- for (let i = 0; i < entities.length; i++) {
83
- entities[i].__orm.originalData = {};
84
- }
85
- }
86
-
87
- // Uses a pg-specific syntax to issue a bulk update
88
- async function batchUpdate(knex: Knex, meta: EntityMetadata<any>, entities: Entity[]): Promise<void> {
89
- // Get the unique set of fields that are changed across all of the entities (of this type) we want to bulk update
90
- const changedFields = new Set<string>();
91
- // Id doesn't change, but we need it for our WHERE clause
92
- changedFields.add("id");
93
- entities.forEach((entity) => {
94
- Object.keys(entity.__orm.originalData).forEach((key) => changedFields.add(key));
95
- });
96
-
97
- // Sometimes with derived fields, an instance will be marked as an update, but if the derived field hasn't changed,
98
- // it'll be a noop, so just short-circuit if it looks like that happened, i.e. we have no changed fields.
99
- if (changedFields.size === 1) {
100
- return;
101
- }
102
-
103
- // This currently assumes a 1-to-1 field-to-column mapping.
104
- const columns = meta.columns.filter((c) => changedFields.has(c.fieldName));
105
- const bindings: any[][] = columns.map(() => []);
106
- for (const entity of entities) {
107
- columns.forEach((c, i) => {
108
- bindings[i].push(c.serde.getFromEntity(entity.__orm.data) ?? null);
109
- });
110
- }
111
- await knex.raw(
112
- cleanSql(`
113
- UPDATE ${meta.tableName}
114
- SET ${columns
115
- .filter((c) => c.columnName !== "id")
116
- .map((c) => `"${c.columnName}" = data."${c.columnName}"`)
117
- .join(", ")}
118
- FROM (select ${columns.map((c) => `unnest(?::${c.dbType}[]) as "${c.columnName}"`).join(", ")}) as data
119
- WHERE ${meta.tableName}.id = data.id
120
- `),
121
- bindings,
122
- );
123
- entities.forEach((entity) => (entity.__orm.originalData = {}));
124
- }
125
-
126
- async function batchDelete(knex: Knex, meta: EntityMetadata<any>, entities: Entity[]): Promise<void> {
127
- await knex(meta.tableName)
128
- .del()
129
- .whereIn(
130
- "id",
131
- entities.map((e) => keyToNumber(meta, e.id!).toString()),
132
- );
133
- entities.forEach((entity) => (entity.__orm.deleted = "deleted"));
134
- }
135
-
136
- function cleanSql(sql: string): string {
137
- return sql.trim().replace(/\n/g, "").replace(/ +/g, " ");
138
- }
139
-
140
- /**
141
- * Scans `entities` for new/updated entities and arranges them per-type in entity order.
142
- *
143
- * This currently assumes the entity types in the schema can be topographically sorted
144
- * and have no cycles, i.e. `books` always depend on `authors` (due to the `books.author_id`
145
- * foreign key), but `authors` never (via a required foreign key) depend on `books`.
146
- */
147
- export function sortEntities(entities: Entity[]): Record<string, Todo> {
148
- const todos: Record<string, Todo> = {};
149
- for (const entity of entities) {
150
- if (entity.isPendingFlush) {
151
- const todo = getTodo(todos, entity);
152
- if (entity.isPendingDelete) {
153
- todo.deletes.push(entity);
154
- } else if (entity.isNewEntity) {
155
- todo.inserts.push(entity);
156
- } else {
157
- todo.updates.push(entity);
158
- }
159
- }
160
- }
161
- return todos;
162
- }
163
-
164
- /** getOrSets a `Todo` for `entity` in `todos`. */
165
- export function getTodo(todos: Record<string, Todo>, entity: Entity): Todo {
166
- const meta = getMetadata(entity);
167
- let todo = todos[meta.type];
168
- if (!todo) {
169
- todo = { metadata: entity.__orm.metadata, inserts: [], updates: [], deletes: [], validates: [] };
170
- todos[meta.type] = todo;
171
- }
172
- return todo;
173
- }
174
-
175
- export async function flushJoinTables(knex: Knex, joinRows: Record<string, JoinRowTodo>): Promise<void> {
176
- for await (const [joinTableName, { m2m, newRows, deletedRows }] of Object.entries(joinRows)) {
177
- if (newRows.length > 0) {
178
- const ids = await knex
179
- .batchInsert(
180
- joinTableName,
181
- newRows.map((row) => {
182
- // The rows in EntityManager.joinRows point to entities, change those to ints
183
- const { id, created_at, m2m, ...fkColumns } = row;
184
- Object.keys(fkColumns).forEach((key) => {
185
- const meta = key == m2m.columnName ? getMetadata(m2m.entity) : m2m.otherMeta;
186
- fkColumns[key] = keyToNumber(meta, maybeResolveReferenceToId(fkColumns[key]));
187
- });
188
- return fkColumns;
189
- }),
190
- )
191
- .returning("id");
192
- for (let i = 0; i < ids.length; i++) {
193
- newRows[i].id = ids[i];
194
- }
195
- }
196
- if (deletedRows.length > 0) {
197
- // `remove`s that were done against unloaded ManyToManyCollections will not have row ids
198
- const [haveIds, noIds] = partition(deletedRows, (r) => r.id !== -1);
199
-
200
- if (haveIds.length > 0) {
201
- await knex(joinTableName)
202
- .del()
203
- .whereIn(
204
- "id",
205
- haveIds.map((e) => e.id!),
206
- );
207
- }
208
-
209
- if (noIds.length > 0) {
210
- const data = noIds.map(
211
- (e) =>
212
- [
213
- deTagIds(m2m.meta, [maybeResolveReferenceToId(e[m2m.columnName])!])[0],
214
- deTagIds(m2m.otherMeta, [maybeResolveReferenceToId(e[m2m.otherColumnName])!])[0],
215
- ] as any,
216
- );
217
- await knex(joinTableName).del().whereIn([m2m.columnName, m2m.otherColumnName], data);
218
- }
219
- }
220
- }
221
- }
222
-
223
- interface JoinRowTodo {
224
- // Store the m2m reference (either side of the m2m, it doesn't matter which) to help tag/untag the foreign keys
225
- m2m: ManyToManyCollection<any, any>;
226
- newRows: JoinRow[];
227
- deletedRows: JoinRow[];
228
- }
229
-
230
- export function sortJoinRows(joinRows: Record<string, JoinRow[]>): Record<string, JoinRowTodo> {
231
- const todos: Record<string, JoinRowTodo> = {};
232
- for (const [joinTableName, rows] of Object.entries(joinRows)) {
233
- const newRows = rows.filter((r) => r.id === undefined && r.deleted !== true);
234
- const deletedRows = rows.filter((r) => r.id !== undefined && r.deleted === true);
235
- if (newRows.length > 0 || deletedRows.length > 0) {
236
- todos[joinTableName] = { newRows, deletedRows, m2m: rows[0].m2m };
237
- }
238
- }
239
- return todos;
240
- }
@@ -1,289 +0,0 @@
1
- import Knex, { QueryBuilder } from "knex";
2
- import {
3
- ColumnMeta,
4
- Entity,
5
- EntityConstructor,
6
- entityLimit,
7
- EntityMetadata,
8
- FilterOf,
9
- getMetadata,
10
- isEntity,
11
- OrderOf,
12
- } from "./EntityManager";
13
- import { ForeignKeySerde } from "./serde";
14
- import { fail } from "./utils";
15
-
16
- export type OrderBy = "ASC" | "DESC";
17
-
18
- export type BooleanFilter<N> = true | false | N;
19
-
20
- export type ValueFilter<V, N> =
21
- | V
22
- | V[]
23
- | N
24
- // Both eq and in are redundant with `V` and `V[]` above but are convenient for matching GQL filter APIs
25
- | { eq: V | N }
26
- | { in: V[] }
27
- | { gt: V }
28
- | { gte: V }
29
- | { ne: V | N }
30
- | { lt: V }
31
- | { lte: V }
32
- | { like: V }
33
- | { ilike: V };
34
-
35
- // For filtering by a foreign key T, i.e. either joining/recursing into with FilterQuery<T>, or matching it is null/not null/etc.
36
- export type EntityFilter<T, I, F, N> = T | I | I[] | F | N | { ne: T | I | N };
37
-
38
- export type BooleanGraphQLFilter = true | false | null;
39
-
40
- export type Primitive = string | boolean | Date | number;
41
-
42
- /** This essentially matches the ValueFilter but with looser types to placate GraphQL. */
43
- export type ValueGraphQLFilter<V> =
44
- | {
45
- eq?: V | null;
46
- in?: V[] | null;
47
- gt?: V | null;
48
- gte?: V | null;
49
- ne?: V | null;
50
- lt?: V | null;
51
- lte?: V | null;
52
- like?: V | null;
53
- ilike?: V | null;
54
- }
55
- | { op: Operator; value: Primitive }
56
- | V
57
- | V[]
58
- | null;
59
-
60
- export type EnumGraphQLFilter<V> = V[] | null | undefined;
61
-
62
- /** A GraphQL version of EntityFilter. */
63
- export type EntityGraphQLFilter<T, I, F> = T | I | I[] | F | { ne: T | I } | null | undefined;
64
-
65
- const operators = ["eq", "gt", "gte", "ne", "lt", "lte", "like", "ilike", "in"] as const;
66
- export type Operator = typeof operators[number];
67
- const opToFn: Record<Operator, string> = {
68
- eq: "=",
69
- gt: ">",
70
- gte: ">=",
71
- ne: "!=",
72
- lt: "<",
73
- lte: "<=",
74
- like: "LIKE",
75
- ilike: "ILIKE",
76
- in: "...",
77
- };
78
-
79
- export type FilterAndSettings<T> = {
80
- where: FilterOf<T>;
81
- orderBy?: OrderOf<T>;
82
- limit?: number;
83
- offset?: number;
84
- };
85
-
86
- /**
87
- * Builds the SQL/knex queries for `EntityManager.find` calls.
88
- *
89
- * Note this is generally for our own internal implementation details and not meant to
90
- * be a user-facing QueryBuilder, i.e. users should use Knex for that and just use SQL
91
- * directly (for any non-trivial queries that `EntityManager.find` does not support).
92
- */
93
- export function buildQuery<T extends Entity>(
94
- knex: Knex,
95
- type: EntityConstructor<T>,
96
- filter: FilterAndSettings<T>,
97
- ): QueryBuilder<{}, unknown[]> {
98
- const meta = getMetadata(type);
99
- const { where, orderBy, limit, offset } = filter;
100
-
101
- const aliases: Record<string, number> = {};
102
- function getAlias(tableName: string): string {
103
- const abbrev = abbreviation(tableName);
104
- const i = aliases[abbrev] || 0;
105
- aliases[abbrev] = i + 1;
106
- return `${abbrev}${i}`;
107
- }
108
-
109
- const alias = getAlias(meta.tableName);
110
- let query: QueryBuilder<any, any> = knex.select<unknown>(`${alias}.*`).from(`${meta.tableName} AS ${alias}`);
111
-
112
- // Define a function for recursively adding joins & filters
113
- function addClauses(
114
- meta: EntityMetadata<any>,
115
- alias: string,
116
- where: object | undefined,
117
- orderBy: object | undefined,
118
- ): void {
119
- // Combine the where and orderBy keys so that we can add them to aliases as that same time
120
- const keys = [...(where ? Object.keys(where) : []), ...(orderBy ? Object.keys(orderBy) : [])];
121
-
122
- keys.forEach((key) => {
123
- const column = meta.columns.find((c) => c.fieldName === key) || fail(`${key} not found`);
124
-
125
- // We may/may not have a where clause or orderBy for this key, but we should have at least one of them.
126
- const clause = where && (where as any)[key];
127
- const hasClause = where && key in where;
128
- const order = orderBy && (orderBy as any)[key];
129
- const hasOrder = !!order;
130
-
131
- if (column.serde instanceof ForeignKeySerde) {
132
- // Add `otherTable.column = ...` clause, unless `key` is not in `where`, i.e. there is only an orderBy for this fk
133
- let [whereNeedsJoin, _query] = hasClause ? addForeignKeyClause(query, alias, column, clause) : [false, query];
134
- query = _query;
135
- if (whereNeedsJoin || hasOrder) {
136
- // Add a join for this column
137
- const otherMeta = column.serde.otherMeta();
138
- const otherAlias = getAlias(otherMeta.tableName);
139
- query = query.innerJoin(
140
- `${otherMeta.tableName} AS ${otherAlias}`,
141
- `${alias}.${column.columnName}`,
142
- `${otherAlias}.id`,
143
- );
144
- // Then recurse to add its conditions to the query
145
- addClauses(otherMeta, otherAlias, whereNeedsJoin ? clause : undefined, hasOrder ? order : undefined);
146
- }
147
- } else {
148
- query = hasClause ? addPrimitiveClause(query, alias, column, clause) : query;
149
- // This is not a foreign key column, so it'll have the primitive filters/order bys
150
- if (order) {
151
- query = query.orderBy(`${alias}.${column.columnName}`, order);
152
- }
153
- }
154
- });
155
- }
156
-
157
- addClauses(meta, alias, where as object, orderBy as object);
158
-
159
- // Even if they already added orders, add id as the last one to get deterministic output
160
- query = query.orderBy(`${alias}.id`);
161
- query = query.limit(limit || entityLimit);
162
- if (offset) {
163
- query = query.offset(offset);
164
- }
165
-
166
- return query as QueryBuilder<{}, unknown[]>;
167
- }
168
-
169
- function abbreviation(tableName: string): string {
170
- return tableName
171
- .split("_")
172
- .map((w) => w[0])
173
- .join("");
174
- }
175
-
176
- function addForeignKeyClause(
177
- query: QueryBuilder,
178
- alias: string,
179
- column: ColumnMeta,
180
- clause: any,
181
- ): [boolean, QueryBuilder] {
182
- // I.e. this could be { authorFk: authorEntity | null | id | { ...recurse... } }
183
- const clauseKeys = typeof clause === "object" && clause !== null ? Object.keys(clause as object) : [];
184
- if (isEntity(clause) || typeof clause == "string" || Array.isArray(clause)) {
185
- // I.e. { authorFk: authorEntity | id | id[] }
186
- if (isEntity(clause) && clause.id === undefined) {
187
- // The user is filtering on an unsaved entity, which will just never have any rows, so throw in -1
188
- return [false, query.where(`${alias}.${column.columnName}`, -1)];
189
- } else if (Array.isArray(clause)) {
190
- return [
191
- false,
192
- query.whereIn(
193
- `${alias}.${column.columnName}`,
194
- clause.map((id) => column.serde.mapToDb(id)),
195
- ),
196
- ];
197
- } else {
198
- return [false, query.where(`${alias}.${column.columnName}`, column.serde.mapToDb(clause))];
199
- }
200
- } else if (clause === null || clause === undefined) {
201
- // I.e. { authorFk: null | undefined }
202
- return [false, query.whereNull(`${alias}.${column.columnName}`)];
203
- } else if (clauseKeys.length === 1 && clauseKeys[0] === "id") {
204
- // I.e. { authorFk: { id: string } } || { authorFk: { id: string[] } }
205
- // If only querying on the id, we can skip the join
206
- const value = (clause as any)["id"];
207
- if (Array.isArray(value)) {
208
- return [
209
- false,
210
- query.whereIn(
211
- `${alias}.${column.columnName}`,
212
- value.map((id) => column.serde.mapToDb(id)),
213
- ),
214
- ];
215
- } else {
216
- return [false, query.where(`${alias}.${column.columnName}`, column.serde.mapToDb(value))];
217
- }
218
- } else if (clauseKeys.length === 1 && clauseKeys[0] === "ne") {
219
- // I.e. { authorFk: { ne: string | null | undefined } }
220
- const value = (clause as any)["ne"];
221
- if (value === null || value === undefined) {
222
- return [false, query.whereNotNull(`${alias}.${column.columnName}`)];
223
- } else if (typeof value === "string") {
224
- return [false, query.whereNot(`${alias}.${column.columnName}`, column.serde.mapToDb(value))];
225
- } else {
226
- throw new Error("Not implemented");
227
- }
228
- } else {
229
- // I.e. { authorFk: { ...authorFilter... } }
230
- return [clause !== undefined, query];
231
- }
232
- }
233
-
234
- function addPrimitiveClause(query: QueryBuilder, alias: string, column: ColumnMeta, clause: any): QueryBuilder {
235
- if (clause && typeof clause === "object" && operators.find((op) => Object.keys(clause).includes(op))) {
236
- // I.e. `{ primitiveField: { gt: value } }`
237
- const op = Object.keys(clause)[0] as Operator;
238
- return addPrimitiveOperator(query, alias, column, op, (clause as any)[op]);
239
- } else if (clause && typeof clause === "object" && "op" in clause) {
240
- // I.e. { primitiveField: { op: "gt", value: 1 } }`
241
- return addPrimitiveOperator(query, alias, column, clause.op, clause.value);
242
- } else if (Array.isArray(clause)) {
243
- // I.e. `{ primitiveField: value[] }`
244
- return query.whereIn(
245
- `${alias}.${column.columnName}`,
246
- clause.map((v) => column.serde.mapToDb(v)),
247
- );
248
- } else if (clause === null) {
249
- // I.e. `{ primitiveField: null }`
250
- return query.whereNull(`${alias}.${column.columnName}`);
251
- } else if (clause === undefined) {
252
- // I.e. `{ primitiveField: undefined }`
253
- // Currently we treat this like a partial filter, i.e. don't include it. Seems odd
254
- // unless this is opt-in, i.e. maybe only do this for `findGql`?
255
- return query;
256
- } else if (clause !== undefined) {
257
- // I.e. `{ primitiveField: value }`
258
- // TODO In theory could add a addToQuery method to Serde to generalize this to multi-columns fields.
259
- return query.where(`${alias}.${column.columnName}`, column.serde.mapToDb(clause));
260
- } else {
261
- return fail(`Unhandled primitive clause ${clause}`);
262
- }
263
- }
264
-
265
- function addPrimitiveOperator(
266
- query: QueryBuilder,
267
- alias: string,
268
- column: ColumnMeta,
269
- op: Operator,
270
- value: any,
271
- ): QueryBuilder {
272
- if (value === null || value === undefined) {
273
- if (op === "ne") {
274
- return query.whereNotNull(`${alias}.${column.columnName}`);
275
- } else if (op === "eq") {
276
- return query.whereNull(`${alias}.${column.columnName}`);
277
- } else {
278
- throw new Error("Only ne is supported when the value is undefined or null");
279
- }
280
- } else if (op === "in") {
281
- return query.whereIn(
282
- `${alias}.${column.columnName}`,
283
- (value as Array<any>).map((v) => column.serde.mapToDb(v)),
284
- );
285
- } else {
286
- const fn = opToFn[op] || fail(`Invalid operator ${op}`);
287
- return query.where(`${alias}.${column.columnName}`, fn, column.serde.mapToDb(value));
288
- }
289
- }
package/src/changes.ts DELETED
@@ -1,40 +0,0 @@
1
- import { Entity, IdOf, OptsOf } from "./EntityManager";
2
-
3
- /** Exposes a field's changed/original value in each entity's `this.changes` property. */
4
- export interface FieldStatus<T> {
5
- hasChanged: boolean;
6
- originalValue?: T;
7
- }
8
-
9
- type NullOrDefinedOr<T> = T | null | undefined;
10
- type ExcludeNever<T> = Pick<T, { [P in keyof T]: T[P] extends never ? never : P }[keyof T]>;
11
-
12
- /**
13
- * Creates the `this.changes.firstName` changes API for a given entity `T`.
14
- *
15
- * Specifically we use the fields from OptsOf but:
16
- *
17
- * - Exclude collections
18
- * - Convert entity types to id types to match what is stored in originalData
19
- */
20
- export type Changes<T extends Entity> = ExcludeNever<
21
- {
22
- [P in keyof OptsOf<T>]-?: OptsOf<T>[P] extends NullOrDefinedOr<infer U>
23
- ? U extends Array<any>
24
- ? never
25
- : U extends Entity
26
- ? FieldStatus<IdOf<U>>
27
- : FieldStatus<U>
28
- : never;
29
- }
30
- >;
31
-
32
- export function newChangesProxy<T extends Entity>(entity: T): Changes<T> {
33
- return new Proxy(entity, {
34
- get(target, p: PropertyKey): FieldStatus<any> {
35
- const originalValue = typeof p === "string" && entity.__orm.originalData[p];
36
- const hasChanged = typeof p === "string" && p in entity.__orm.originalData && entity.id !== undefined;
37
- return { hasChanged, originalValue };
38
- },
39
- }) as any;
40
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * Defines common hooks that relations can respond to to keep the entity graph in sync.
3
- */
4
- export abstract class AbstractRelationImpl<U> {
5
- /** Called with the opts from a `new` or `em.create` call, i.e. on a new entity. */
6
- abstract setFromOpts(value: U): void;
7
-
8
- /** Called on each relation of a new entity, since we know it defacto can be marked as loaded. */
9
- abstract initializeForNewEntity(): void;
10
-
11
- /** Similar to setFromOpts, but called post-construction. */
12
- abstract set(value: U): void;
13
-
14
- /** Called on `EntityManager.refresh()` to reload the collection from the latest db values. */
15
- abstract async refreshIfLoaded(): Promise<void>;
16
-
17
- /**
18
- * Called when our entity has been `EntityManager.delete`'d _and_ `EntityManager.flush` is being called,
19
- * so we can unset any foreign keys to the being-deleted entity.
20
- */
21
- abstract async onEntityDeletedAndFlushing(): Promise<void>;
22
-
23
- /**
24
- * Called when our entity has been `EntityManager.delete`'d so that we can run any special behavior
25
- * like cascades
26
- */
27
- abstract onEntityDelete(): void;
28
- }