@entity-access/entity-access 1.0.251 → 1.0.253

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 (102) hide show
  1. package/.vscode/launch.json +2 -1
  2. package/dist/common/symbols/symbols.d.ts +1 -0
  3. package/dist/common/symbols/symbols.d.ts.map +1 -1
  4. package/dist/common/symbols/symbols.js +1 -0
  5. package/dist/common/symbols/symbols.js.map +1 -1
  6. package/dist/compiler/QueryCompiler.d.ts +3 -3
  7. package/dist/decorators/ForeignKey.d.ts +8 -7
  8. package/dist/decorators/ForeignKey.d.ts.map +1 -1
  9. package/dist/decorators/ForeignKey.js +43 -8
  10. package/dist/decorators/ForeignKey.js.map +1 -1
  11. package/dist/decorators/IColumn.d.ts +6 -3
  12. package/dist/decorators/IColumn.d.ts.map +1 -1
  13. package/dist/decorators/Relate.d.ts.map +1 -1
  14. package/dist/decorators/Relate.js +8 -6
  15. package/dist/decorators/Relate.js.map +1 -1
  16. package/dist/entity-query/EntityType.d.ts +5 -1
  17. package/dist/entity-query/EntityType.d.ts.map +1 -1
  18. package/dist/entity-query/EntityType.js +57 -25
  19. package/dist/entity-query/EntityType.js.map +1 -1
  20. package/dist/migrations/postgres/PostgresAutomaticMigrations.js +1 -1
  21. package/dist/migrations/postgres/PostgresAutomaticMigrations.js.map +1 -1
  22. package/dist/migrations/sql-server/SqlServerAutomaticMigrations.js +1 -1
  23. package/dist/migrations/sql-server/SqlServerAutomaticMigrations.js.map +1 -1
  24. package/dist/model/EntityModel.js +2 -2
  25. package/dist/model/EntityModel.js.map +1 -1
  26. package/dist/model/EntitySource.d.ts +6 -1
  27. package/dist/model/EntitySource.d.ts.map +1 -1
  28. package/dist/model/EntitySource.js.map +1 -1
  29. package/dist/model/SourceExpression.d.ts +1 -22
  30. package/dist/model/SourceExpression.d.ts.map +1 -1
  31. package/dist/model/SourceExpression.js +116 -98
  32. package/dist/model/SourceExpression.js.map +1 -1
  33. package/dist/model/changes/ChangeEntry.d.ts.map +1 -1
  34. package/dist/model/changes/ChangeEntry.js +62 -25
  35. package/dist/model/changes/ChangeEntry.js.map +1 -1
  36. package/dist/model/changes/ChangeSet.d.ts +2 -1
  37. package/dist/model/changes/ChangeSet.d.ts.map +1 -1
  38. package/dist/model/changes/ChangeSet.js +4 -3
  39. package/dist/model/changes/ChangeSet.js.map +1 -1
  40. package/dist/model/identity/IdentityMap.d.ts +23 -0
  41. package/dist/model/identity/IdentityMap.d.ts.map +1 -0
  42. package/dist/model/identity/IdentityMap.js +113 -0
  43. package/dist/model/identity/IdentityMap.js.map +1 -0
  44. package/dist/model/identity/RelationMapper.d.ts +2 -3
  45. package/dist/model/identity/RelationMapper.d.ts.map +1 -1
  46. package/dist/model/identity/RelationMapper.js +60 -27
  47. package/dist/model/identity/RelationMapper.js.map +1 -1
  48. package/dist/model/identity/SearchIndex.d.ts +17 -0
  49. package/dist/model/identity/SearchIndex.d.ts.map +1 -0
  50. package/dist/model/identity/SearchIndex.js +109 -0
  51. package/dist/model/identity/SearchIndex.js.map +1 -0
  52. package/dist/model/verification/VerificationSession.d.ts +1 -1
  53. package/dist/model/verification/VerificationSession.d.ts.map +1 -1
  54. package/dist/model/verification/VerificationSession.js +18 -16
  55. package/dist/model/verification/VerificationSession.js.map +1 -1
  56. package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
  57. package/dist/query/ast/ExpressionToSql.js +74 -52
  58. package/dist/query/ast/ExpressionToSql.js.map +1 -1
  59. package/dist/query/expander/QueryExpander.d.ts.map +1 -1
  60. package/dist/query/expander/QueryExpander.js +41 -10
  61. package/dist/query/expander/QueryExpander.js.map +1 -1
  62. package/dist/query/parser/ArrowToExpression.d.ts +16 -5
  63. package/dist/query/parser/ArrowToExpression.d.ts.map +1 -1
  64. package/dist/query/parser/ArrowToExpression.js +45 -25
  65. package/dist/query/parser/ArrowToExpression.js.map +1 -1
  66. package/dist/tests/db-tests/tests/multi-fk-tests.d.ts +3 -0
  67. package/dist/tests/db-tests/tests/multi-fk-tests.d.ts.map +1 -0
  68. package/dist/tests/db-tests/tests/multi-fk-tests.js +38 -0
  69. package/dist/tests/db-tests/tests/multi-fk-tests.js.map +1 -0
  70. package/dist/tests/expressions/left-joins/child-joins.js +7 -7
  71. package/dist/tests/model/ShoppingContext.d.ts +9 -0
  72. package/dist/tests/model/ShoppingContext.d.ts.map +1 -1
  73. package/dist/tests/model/ShoppingContext.js +34 -0
  74. package/dist/tests/model/ShoppingContext.js.map +1 -1
  75. package/dist/tests/security/tests/include-items.d.ts.map +1 -1
  76. package/dist/tests/security/tests/include-items.js +1 -0
  77. package/dist/tests/security/tests/include-items.js.map +1 -1
  78. package/dist/tsconfig.tsbuildinfo +1 -1
  79. package/package.json +1 -1
  80. package/src/common/symbols/symbols.ts +2 -1
  81. package/src/decorators/ForeignKey.ts +66 -28
  82. package/src/decorators/IColumn.ts +4 -3
  83. package/src/decorators/Relate.ts +8 -6
  84. package/src/entity-query/EntityType.ts +60 -26
  85. package/src/migrations/postgres/PostgresAutomaticMigrations.ts +1 -1
  86. package/src/migrations/sql-server/SqlServerAutomaticMigrations.ts +1 -1
  87. package/src/model/EntityModel.ts +2 -2
  88. package/src/model/EntitySource.ts +6 -1
  89. package/src/model/SourceExpression.ts +132 -132
  90. package/src/model/changes/ChangeEntry.ts +68 -25
  91. package/src/model/changes/ChangeSet.ts +4 -3
  92. package/src/model/identity/IdentityMap.ts +126 -0
  93. package/src/model/identity/RelationMapper.ts +71 -27
  94. package/src/model/identity/SearchIndex.ts +120 -0
  95. package/src/model/verification/VerificationSession.ts +19 -16
  96. package/src/query/ast/ExpressionToSql.ts +77 -61
  97. package/src/query/expander/QueryExpander.ts +52 -28
  98. package/src/query/parser/ArrowToExpression.ts +50 -26
  99. package/src/tests/db-tests/tests/multi-fk-tests.ts +46 -0
  100. package/src/tests/expressions/left-joins/child-joins.ts +7 -7
  101. package/src/tests/model/ShoppingContext.ts +32 -0
  102. package/src/tests/security/tests/include-items.ts +1 -0
@@ -98,7 +98,7 @@ export class QueryExpander {
98
98
  // let where: Expression;
99
99
  // let joinWhere: Expression;
100
100
 
101
- const fk = relation.fkColumn ?? relation.relatedRelation.fkColumn;
101
+ const fk = relation.fkMap ?? relation.relatedRelation.fkMap;
102
102
 
103
103
  key += "." + relation.name;
104
104
 
@@ -107,15 +107,17 @@ export class QueryExpander {
107
107
  return relationSet;
108
108
  }
109
109
 
110
+ let where: Expression;
111
+
110
112
  if(relation.isInverseRelation) {
111
113
 
112
- const keyColumn = model.keys[0].columnName;
113
- let columnName = fk.columnName;
114
- // for inverse relation, we need to
115
- // use primary key of current model
116
- if (!relation.isCollection) {
117
- columnName = select.model.keys[0].columnName;
118
- }
114
+ // const keyColumn = model.keys[0].columnName;
115
+ // let columnName = fk.columnName;
116
+ // // for inverse relation, we need to
117
+ // // use primary key of current model
118
+ // if (!relation.isCollection) {
119
+ // columnName = select.model.keys[0].columnName;
120
+ // }
119
121
 
120
122
 
121
123
  const joins = (select.joins ??= []);
@@ -124,21 +126,32 @@ export class QueryExpander {
124
126
  // This join has to be INNER JOIN as we are only interested
125
127
  // in the results that matches parent query exactly
126
128
 
129
+ for (const { fkColumn, relatedKeyColumn } of relation.relatedRelation.fkMap) {
130
+ const joinColumn = fkColumn.entityType === joinParameter.model ? fkColumn : relatedKeyColumn;
131
+ const relatedColumn = relatedKeyColumn.entityType === select.sourceParameter.model ? relatedKeyColumn : fkColumn;
132
+ const joinOn = Expression.equal(
133
+ Expression.member(joinParameter, Expression.identifier(joinColumn.columnName)),
134
+ Expression.member(select.sourceParameter, Expression.identifier(relatedColumn.columnName))
135
+ );
136
+ where = where ? Expression.logicalAnd(where, joinOn) : joinOn;
137
+ }
138
+
127
139
  joins.push(JoinExpression.create({
128
140
  joinType: "INNER",
129
141
  source: { ... parent },
130
142
  as: joinParameter,
131
143
  model: parent.model,
132
- where: Expression.equal(
133
- Expression.member(
134
- joinParameter,
135
- Expression.identifier(keyColumn)
136
- ),
137
- Expression.member(
138
- select.sourceParameter,
139
- Expression.identifier(columnName)
140
- )
141
- )
144
+ where
145
+ // where: Expression.equal(
146
+ // Expression.member(
147
+ // joinParameter,
148
+ // Expression.identifier(keyColumn)
149
+ // ),
150
+ // Expression.member(
151
+ // select.sourceParameter,
152
+ // Expression.identifier(columnName)
153
+ // )
154
+ // )
142
155
  }));
143
156
 
144
157
  // if (parent.where) {
@@ -192,22 +205,33 @@ export class QueryExpander {
192
205
  // This join has to be INNER JOIN as we are only interested
193
206
  // in the results that matches parent query exactly
194
207
 
208
+ for (const { fkColumn, relatedKeyColumn } of relation.fkMap) {
209
+ const joinOn = Expression.equal(
210
+ Expression.member(selectJoinParameter,
211
+ Expression.identifier(fkColumn.columnName)),
212
+ Expression.member(select.sourceParameter,
213
+ Expression.identifier(relatedKeyColumn.columnName))
214
+ );
215
+ where = where ? Expression.logicalAnd(where, joinOn) : joinOn;
216
+ }
217
+
195
218
  selectJoins.push(JoinExpression.create({
196
219
  joinType: "INNER",
197
220
  source: { ... parent },
198
221
  as: selectJoinParameter,
199
222
  model: parent.model,
223
+ where
200
224
  // model,
201
- where: Expression.equal(
202
- Expression.member(
203
- selectJoinParameter,
204
- Expression.identifier(fk.columnName)
205
- ),
206
- Expression.member(
207
- select.sourceParameter,
208
- Expression.identifier(relation.relatedEntity.keys[0].columnName)
209
- )
210
- )
225
+ // where: Expression.equal(
226
+ // Expression.member(
227
+ // selectJoinParameter,
228
+ // Expression.identifier(fk.columnName)
229
+ // ),
230
+ // Expression.member(
231
+ // select.sourceParameter,
232
+ // Expression.identifier(relation.relatedEntity.keys[0].columnName)
233
+ // )
234
+ // )
211
235
  }));
212
236
 
213
237
  this.include.push(select);
@@ -10,8 +10,19 @@ type IQueryFragment = string | { name?: string, value?: any };
10
10
 
11
11
  const parsedCache = new TimedCache<string, bpe.Node>();
12
12
 
13
+ const parameterCacheSymbol = Symbol("parameterCacheSymbol");
14
+
15
+ const defaultObject = {};
16
+
13
17
  export default class ArrowToExpression extends BabelVisitor<Expression> {
14
18
 
19
+ /**
20
+ * Parses lambda expression function or string to set simple SQL ready AST which
21
+ * transforms basic && to AND etc.
22
+ * @param fx function code (usually lambda expression)
23
+ * @param target parameter target
24
+ * @returns Parsed expression
25
+ */
15
26
  public static transform(fx: (p: any) => (x: any) => any, target?: ParameterExpression) {
16
27
  const key = fx.toString();
17
28
  const node = parsedCache.getOrCreate(key, fx, (k, f) => {
@@ -21,41 +32,54 @@ export default class ArrowToExpression extends BabelVisitor<Expression> {
21
32
  return this.transformUncached(node, target);
22
33
  }
23
34
 
24
- private static transformUncached(node: bpe.Node, target?: ParameterExpression) {
35
+ /**
36
+ * Since expression parsed as a different parameter in nested lambda (p) => (x) => x..,
37
+ * we need to replace x with provided target to bind x with respective ParameterExpression.
38
+ * As ParameterExpression contains the type and model associated with the table represented by `x`.
39
+ * @param node parsed node
40
+ * @param target parameter to replace
41
+ * @returns transformed node
42
+ */
43
+ private static transformUncached(node: bpe.Node, tx?: ParameterExpression) {
25
44
 
26
- if (node.type !== "ArrowFunctionExpression") {
27
- throw new Error("Expecting an arrow function");
28
- }
45
+ const cache = node[parameterCacheSymbol] ??= new TimedCache<ParameterExpression,bpe.Node>();
29
46
 
30
- const params = [] as ParameterExpression[];
47
+ return cache.getOrCreate(tx ?? defaultObject, tx, (_, target) => {
31
48
 
32
- for (const iterator of node.params) {
33
- if (iterator.type !== "Identifier") {
34
- throw new Error("Expecting an identifier");
49
+ if (node.type !== "ArrowFunctionExpression") {
50
+ throw new Error("Expecting an arrow function");
35
51
  }
36
- params.push(ParameterExpression.create({ name: iterator.name }));
37
- }
38
52
 
39
- let body = node.body;
40
- if (body.type !== "ArrowFunctionExpression") {
41
- throw new Error("Expecting an arrow function");
42
- }
53
+ const params = [] as ParameterExpression[];
43
54
 
44
- const firstTarget = body.params[0];
45
- if (firstTarget.type !== "Identifier") {
46
- throw new Error("Expecting an identifier");
47
- }
55
+ for (const iterator of node.params) {
56
+ if (iterator.type !== "Identifier") {
57
+ throw new Error("Expecting an identifier");
58
+ }
59
+ params.push(ParameterExpression.create({ name: iterator.name }));
60
+ }
48
61
 
49
- target ??= ParameterExpression.create({ name: firstTarget.name});
62
+ let body = node.body;
63
+ if (body.type !== "ArrowFunctionExpression") {
64
+ throw new Error("Expecting an arrow function");
65
+ }
50
66
 
51
- body = body.body;
67
+ const firstTarget = body.params[0];
68
+ if (firstTarget.type !== "Identifier") {
69
+ throw new Error("Expecting an identifier");
70
+ }
52
71
 
53
- const visitor = new this(params, target, firstTarget.name);
54
- return {
55
- params,
56
- target,
57
- body: visitor.visit(body)
58
- };
72
+ target ??= ParameterExpression.create({ name: firstTarget.name});
73
+
74
+ body = body.body;
75
+
76
+ const visitor = new this(params, target, firstTarget.name);
77
+ return {
78
+ params,
79
+ target,
80
+ body: visitor.visit(body)
81
+ };
82
+ });
59
83
  }
60
84
 
61
85
  public readonly leftJoins: string[] = [];
@@ -0,0 +1,46 @@
1
+ import assert from "assert";
2
+ import { TestConfig } from "../../TestConfig.js";
3
+ import { createContext } from "../../model/createContext.js";
4
+
5
+ export default async function(this: TestConfig) {
6
+
7
+ if (!this.db) {
8
+ return;
9
+ }
10
+
11
+ const context = await createContext(this.driver);
12
+
13
+ const { userID } = await context.users.asQuery().first();
14
+ const { categoryID } = await context.categories.asQuery().first();
15
+
16
+ await context.userCategories.saveDirect({
17
+ mode: "insert",
18
+ changes: {
19
+ userID,
20
+ categoryID
21
+ }
22
+ });
23
+
24
+ await context.userCategoryTags.saveDirect({
25
+ mode: "insert",
26
+ changes: {
27
+ categoryID,
28
+ userID,
29
+ tag: "A"
30
+ }
31
+ });
32
+ await context.userCategoryTags.saveDirect({
33
+ mode: "insert",
34
+ changes: {
35
+ categoryID,
36
+ userID,
37
+ tag: "B"
38
+ }
39
+ });
40
+
41
+ const first = await context.userCategories.asQuery()
42
+ .include((x) => x.tags)
43
+ .first();
44
+
45
+ assert.notEqual(void 0, first.tags);
46
+ }
@@ -14,7 +14,7 @@ FROM products AS p1
14
14
  WHERE EXISTS (SELECT
15
15
  1
16
16
  FROM order_items AS o
17
- WHERE (p1.product_id = o.product_id) AND (o.product_id = $1))`;
17
+ WHERE (o.product_id = $1) AND (p1.product_id = o.product_id))`;
18
18
 
19
19
  const sql2 = `SELECT
20
20
  p1.product_id,
@@ -26,10 +26,10 @@ FROM products AS p1
26
26
  WHERE EXISTS (SELECT
27
27
  1
28
28
  FROM order_items AS o
29
- WHERE (p1.product_id = o.product_id) AND (o.product_id = $1)) AND EXISTS (SELECT
29
+ WHERE (o.product_id = $1) AND (p1.product_id = o.product_id)) AND EXISTS (SELECT
30
30
  1
31
31
  FROM order_items AS o1
32
- WHERE (p1.product_id = o1.product_id) AND (o1.amount > $2))`;
32
+ WHERE (o1.amount > $2) AND (p1.product_id = o1.product_id))`;
33
33
 
34
34
  const sql3 = `SELECT
35
35
  p1.product_id,
@@ -41,11 +41,11 @@ FROM products AS p1
41
41
  WHERE EXISTS (SELECT
42
42
  1
43
43
  FROM order_items AS o
44
- WHERE (p1.product_id = o.product_id) AND (o.product_id = $1)) AND EXISTS (SELECT
44
+ WHERE (o.product_id = $1) AND (p1.product_id = o.product_id)) AND EXISTS (SELECT
45
45
  1
46
46
  FROM order_items AS o1
47
47
  INNER JOIN orders AS o2 ON o1.order_id = o2.order_id
48
- WHERE (p1.product_id = o1.product_id) AND (o2.order_date > $2))`;
48
+ WHERE (o2.order_date > $2) AND (p1.product_id = o1.product_id))`;
49
49
 
50
50
  const productJoin = `SELECT
51
51
  p1.product_id,
@@ -77,10 +77,10 @@ p1.product_description
77
77
  FROM products AS p1
78
78
  WHERE EXISTS
79
79
  (SELECT 1 FROM order_items AS o
80
- WHERE (p1.product_id = o.product_id) AND (o.product_id = $1)) AND
80
+ WHERE (o.product_id = $1) AND (p1.product_id = o.product_id)) AND
81
81
  NOT (EXISTS (SELECT 1 FROM order_items AS o1
82
82
  INNER JOIN orders AS o2 ON o1.order_id = o2.order_id
83
- WHERE (p1.product_id = o1.product_id) AND (o2.order_date > $2)))
83
+ WHERE (o2.order_date > $2) AND (p1.product_id = o1.product_id)))
84
84
  `;
85
85
 
86
86
  export default function() {
@@ -6,6 +6,7 @@ import Index from "../../decorators/Index.js";
6
6
  import DateTime from "../../types/DateTime.js";
7
7
  import { UserFile } from "./UseFile.js";
8
8
  import Sql from "../../sql/Sql.js";
9
+ import MultiForeignKeys from "../../decorators/ForeignKey.js";
9
10
 
10
11
  export const statusPublished = "published";
11
12
 
@@ -34,6 +35,8 @@ export class ShoppingContext extends EntityContext {
34
35
  public userFiles = this.model.register(UserFile);
35
36
 
36
37
  public emailAddresses = this.model.register(EmailAddress);
38
+
39
+ public userCategoryTags = this.model.register(UserCategoryTag);
37
40
  }
38
41
 
39
42
  @Table("Users")
@@ -191,6 +194,35 @@ export class UserCategory {
191
194
  public user: User;
192
195
 
193
196
  public category: Category;
197
+
198
+ public tags: UserCategoryTag[];
199
+ }
200
+
201
+ @Table("UserCategoryTags")
202
+ export class UserCategoryTag {
203
+
204
+ @Column({ key: true, dataType: "BigInt", generated: "identity"})
205
+ tagID: number;
206
+
207
+ @Column({ dataType: "Char", length: 200 })
208
+ tag: string;
209
+
210
+ @Column({ dataType: "BigInt"})
211
+ public userID: number;
212
+
213
+ @Column({ dataType: "Char", length: 200 })
214
+ public categoryID: string;
215
+
216
+ @MultiForeignKeys(UserCategory, {
217
+ inverseProperty: (x) => x.tags,
218
+ foreignKeys: [
219
+ { foreignKey: (x) => x.userID, key: (x) => x.userID },
220
+ { foreignKey: (x) => x.categoryID, key: (x) => x.categoryID}
221
+ ]
222
+ }
223
+ )
224
+ public userCategory: UserCategory;
225
+
194
226
  }
195
227
 
196
228
  @Table("Products")
@@ -15,5 +15,6 @@ export default async function (this: TestConfig) {
15
15
  .first();
16
16
 
17
17
  assert.notEqual(null, order);
18
+ assert.notEqual(undefined, order.orderItems);
18
19
 
19
20
  }