@snowtop/ent 0.1.12 → 0.1.13

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 (44) hide show
  1. package/action/experimental_action.js +2 -2
  2. package/action/operations.js +4 -2
  3. package/core/base.d.ts +12 -10
  4. package/core/clause.d.ts +4 -3
  5. package/core/clause.js +98 -43
  6. package/core/config.d.ts +6 -0
  7. package/core/context.d.ts +6 -14
  8. package/core/context.js +9 -4
  9. package/core/db.js +1 -1
  10. package/core/ent.d.ts +1 -0
  11. package/core/ent.js +30 -11
  12. package/core/loaders/assoc_count_loader.js +1 -1
  13. package/core/loaders/assoc_edge_loader.d.ts +3 -0
  14. package/core/loaders/assoc_edge_loader.js +18 -0
  15. package/core/loaders/object_loader.js +2 -2
  16. package/core/loaders/query_loader.d.ts +3 -3
  17. package/core/loaders/query_loader.js +2 -1
  18. package/core/loaders/raw_count_loader.js +1 -1
  19. package/core/query/assoc_query.d.ts +22 -0
  20. package/core/query/assoc_query.js +101 -2
  21. package/core/query/custom_query.js +2 -9
  22. package/core/query/query.d.ts +1 -0
  23. package/core/query/query.js +72 -11
  24. package/core/query/shared_assoc_test.js +404 -7
  25. package/core/query/shared_test.js +9 -37
  26. package/core/query_impl.d.ts +2 -1
  27. package/core/query_impl.js +25 -7
  28. package/graphql/query/edge_connection.js +2 -2
  29. package/package.json +2 -2
  30. package/parse_schema/parse.d.ts +2 -2
  31. package/parse_schema/parse.js +3 -3
  32. package/schema/struct_field.d.ts +4 -2
  33. package/schema/struct_field.js +33 -4
  34. package/scripts/custom_graphql.js +1 -1
  35. package/testutils/builder.d.ts +1 -1
  36. package/testutils/builder.js +4 -4
  37. package/testutils/ent-graphql-tests/index.js +2 -2
  38. package/testutils/fake_data/fake_contact.js +1 -1
  39. package/testutils/fake_data/fake_event.js +1 -1
  40. package/testutils/fake_data/test_helpers.js +2 -2
  41. package/testutils/fake_data/user_query.js +1 -1
  42. package/testutils/query.d.ts +9 -0
  43. package/testutils/query.js +45 -0
  44. package/testutils/write.js +3 -3
@@ -12,8 +12,14 @@ const builder_1 = require("../../testutils/builder");
12
12
  const luxon_1 = require("luxon");
13
13
  const convert_1 = require("../convert");
14
14
  const test_context_1 = require("../../testutils/context/test_context");
15
+ const query_1 = require("../../testutils/query");
15
16
  function assocTests(ml, global = false) {
16
17
  ml.mock();
18
+ // beforeAll(async () => {
19
+ // // TODO this is needed when we do only in a test here
20
+ // // need to figure this out
21
+ // await createEdges();
22
+ // });
17
23
  describe("custom edge", () => {
18
24
  let user1, user2;
19
25
  beforeEach(async () => {
@@ -58,12 +64,12 @@ function assocTests(ml, global = false) {
58
64
  return execArray?.[3];
59
65
  }
60
66
  function verifyQuery({ length = 1, numQueries = 1, limit = (0, ent_1.getDefaultLimit)(), extraClause = undefined, disablePaginationBump = false, logsStart = 0, direction = "DESC", }) {
61
- const clses = [(0, clause_1.Eq)("id1", ""), (0, clause_1.Eq)("edge_type", "")];
67
+ const clauses = [(0, clause_1.Eq)("id1", ""), (0, clause_1.Eq)("edge_type", "")];
62
68
  if (extraClause) {
63
- clses.push(extraClause);
69
+ clauses.push(extraClause);
64
70
  }
65
71
  if (global) {
66
- clses.push((0, clause_1.Eq)("deleted_at", null));
72
+ clauses.push((0, clause_1.Eq)("deleted_at", null));
67
73
  }
68
74
  expect(ml.logs.length).toBe(length);
69
75
  for (let i = logsStart; i < numQueries; i++) {
@@ -71,7 +77,7 @@ function assocTests(ml, global = false) {
71
77
  let expLimit = disablePaginationBump ? limit : limit + 1;
72
78
  expect(whereClause, `${i}`).toBe(
73
79
  // default limit
74
- `${(0, clause_1.And)(...clses).clause(1)} ORDER BY time ${direction}, id2 ${direction} LIMIT ${expLimit}`);
80
+ `${(0, clause_1.And)(...clauses).clause(1)} ORDER BY time ${direction}, id2 ${direction} LIMIT ${expLimit}`);
75
81
  }
76
82
  }
77
83
  function verifyCountQuery({ length = 1, numQueries = 1 }) {
@@ -132,7 +138,7 @@ function assocTests(ml, global = false) {
132
138
  expect(countMap.size).toBe(this.dataz.length);
133
139
  for (let i = 0; i < this.dataz.length; i++) {
134
140
  let data = this.dataz[i];
135
- expect(countMap.get(data[0].id)).toStrictEqual(test_helpers_1.inputs.length);
141
+ expect(countMap.get(data[0].id)).toBe(test_helpers_1.inputs.length);
136
142
  }
137
143
  verifyCountQuery({ numQueries: 3, length: 3 });
138
144
  }
@@ -141,7 +147,7 @@ function assocTests(ml, global = false) {
141
147
  expect(countMap.size).toBe(this.dataz.length);
142
148
  for (let i = 0; i < this.dataz.length; i++) {
143
149
  let data = this.dataz[i];
144
- expect(countMap.get(data[0].id)).toStrictEqual(data[1].length);
150
+ expect(countMap.get(data[0].id)).toBe(data[1].length);
145
151
  }
146
152
  verifyQuery({
147
153
  length: this.dataz.length,
@@ -260,6 +266,34 @@ function assocTests(ml, global = false) {
260
266
  await filter.testEnts();
261
267
  });
262
268
  });
269
+ function validateQueryIntersectOrUnion(ml, user1, user2) {
270
+ return function (_query, _cursor) {
271
+ // 2 queries for user because privacy
272
+ // 2 queries to fetch edges.
273
+ expect(ml.logs.length).toBe(4);
274
+ const where1 = getWhereClause(ml.logs[1]);
275
+ const clauses1 = [
276
+ (0, clause_1.Eq)("id1", user1.id),
277
+ (0, clause_1.Eq)("edge_type", index_1.EdgeType.UserToFriends),
278
+ ];
279
+ const clauses2 = [
280
+ (0, clause_1.Eq)("id1", user2.id),
281
+ (0, clause_1.Eq)("edge_type", index_1.EdgeType.UserToFriends),
282
+ ];
283
+ if (global) {
284
+ clauses1.push((0, clause_1.Eq)("deleted_at", null));
285
+ clauses2.push((0, clause_1.Eq)("deleted_at", null));
286
+ }
287
+ const clause1 = (0, clause_1.And)(...clauses1);
288
+ expect(where1).toBe(`${clause1.clause(1)} ORDER BY time DESC LIMIT 1000`);
289
+ expect(ml.logs[1].values).toStrictEqual(clause1.values());
290
+ const where2 = getWhereClause(ml.logs[3]);
291
+ const clause2 = (0, clause_1.And)(...clauses2);
292
+ // TODO 1001 vs 1000 here
293
+ expect(where2).toBe(`${clause2.clause(1)} ORDER BY time DESC, id2 DESC LIMIT 1001`);
294
+ expect(ml.logs[3].values).toStrictEqual(clause2.values());
295
+ };
296
+ }
263
297
  class ChainTestQueryFilter {
264
298
  constructor(initialQuery, subsequentQueries, filter, lastHopFilter) {
265
299
  this.initialQuery = initialQuery;
@@ -288,7 +322,7 @@ function assocTests(ml, global = false) {
288
322
  builder.orchestrator.addInboundEdge(this.event2.id, index_1.EdgeType.EventToAttendees, index_1.NodeType.FakeEvent);
289
323
  }
290
324
  await builder.saveX();
291
- return await builder.editedEntX();
325
+ return builder.editedEntX();
292
326
  }));
293
327
  expect(this.friends.length).toBe(test_helpers_1.inputs.length);
294
328
  const count = await index_1.UserToFriendsQuery.query(new viewer_1.IDViewer(this.user.id), this.user.id).queryCount();
@@ -948,6 +982,369 @@ function assocTests(ml, global = false) {
948
982
  expect(info2.hasPreviousPage).toBe(false);
949
983
  });
950
984
  });
985
+ describe("intersect", () => {
986
+ let users = [];
987
+ let user1;
988
+ let user2;
989
+ beforeEach(async () => {
990
+ users = [];
991
+ for (let i = 0; i < 10; i++) {
992
+ const user = await (0, test_helpers_1.createTestUser)();
993
+ users.push(user);
994
+ }
995
+ for (let i = 0; i < 10; i++) {
996
+ const user = users[i];
997
+ // decreasing number of friends for each user
998
+ const candidates = users
999
+ .slice(0, 10 - i)
1000
+ .filter((u) => u.id != user.id);
1001
+ await (0, test_helpers_1.addEdge)(user, index_1.FakeUserSchema, index_1.EdgeType.UserToFriends, false, ...candidates);
1002
+ const count = await index_1.UserToFriendsQuery.query(user.viewer, user).queryRawCount();
1003
+ expect(count, `${i}`).toBe(candidates.length);
1004
+ }
1005
+ user1 = users[0];
1006
+ user2 = users[1];
1007
+ });
1008
+ function getQuery() {
1009
+ return index_1.UserToFriendsQuery.query(user1.viewer, user1.id).__intersect(index_1.UserToFriendsQuery.query(user2.viewer, user2.id));
1010
+ }
1011
+ function getCandidateIDs() {
1012
+ // not the first 2 since that's user1 and user2
1013
+ // not the last one since that's removed from user2's list of friends
1014
+ return users.slice(2, users.length - 1).map((u) => u.id);
1015
+ }
1016
+ test("ids", async () => {
1017
+ const ids = await getQuery().queryIDs();
1018
+ const candidates = getCandidateIDs();
1019
+ expect(ids.length).toBe(candidates.length);
1020
+ expect(ids.sort()).toEqual(candidates.sort());
1021
+ });
1022
+ test("count", async () => {
1023
+ const count = await getQuery().queryCount();
1024
+ const candidates = getCandidateIDs();
1025
+ expect(count).toBe(candidates.length);
1026
+ });
1027
+ test("raw_count", async () => {
1028
+ const count = await getQuery().queryRawCount();
1029
+ // raw count doesn't include the intersection
1030
+ expect(count).toBe(users.length - 1);
1031
+ });
1032
+ test("edges", async () => {
1033
+ const edges = await getQuery().queryEdges();
1034
+ const candidates = getCandidateIDs();
1035
+ expect(edges.length).toBe(candidates.length);
1036
+ // for an intersect, the edge returned is always for the source id
1037
+ for (const edge of edges) {
1038
+ expect(edge.id1).toBe(user1.id);
1039
+ }
1040
+ });
1041
+ test("ents", async () => {
1042
+ const ents = await getQuery().queryEnts();
1043
+ const candidates = getCandidateIDs().sort();
1044
+ expect(ents.length).toBe(candidates.length);
1045
+ expect(ents.map((u) => u.id).sort()).toEqual(candidates.sort());
1046
+ });
1047
+ test("first", async () => {
1048
+ const ents = await getQuery().first(2).queryEnts();
1049
+ expect(ents.length).toBe(2);
1050
+ });
1051
+ test("first. after each cursor", async () => {
1052
+ const edges = await getQuery().queryEdges();
1053
+ const { verify, getCursor } = (0, query_1.getVerifyAfterEachCursorGeneric)(edges, 2, user1, getQuery, ml, validateQueryIntersectOrUnion(ml, user1, user2));
1054
+ // this one intentionally not generic so we know where to stop...
1055
+ expect(edges.length).toBe(7);
1056
+ await verify(0, true, true, undefined);
1057
+ await verify(2, true, true, getCursor(edges[1]));
1058
+ await verify(4, true, true, getCursor(edges[3]));
1059
+ // 1 item, no nextPage
1060
+ await verify(6, true, false, getCursor(edges[5]));
1061
+ await verify(7, false, false, getCursor(edges[6]));
1062
+ });
1063
+ test("multiple intersections", async () => {
1064
+ const query = index_1.UserToFriendsQuery.query(user1.viewer, user1.id).__intersect(index_1.UserToFriendsQuery.query(user2.viewer, user2.id), index_1.UserToFriendsQuery.query(user2.viewer, users[2].id), index_1.UserToFriendsQuery.query(user2.viewer, users[3].id));
1065
+ // user0 => 0-9 - self
1066
+ // user1 => 0-8 - self
1067
+ // user2 => 0-7 - self
1068
+ // user3 => 0-6 -self
1069
+ // 7 users - 4 = 3 since you can't be friends with yourself
1070
+ const candidates = [users[4], users[5], users[6]];
1071
+ const [ids, count, edges, ents] = await Promise.all([
1072
+ query.queryIDs(),
1073
+ query.queryCount(),
1074
+ query.queryEdges(),
1075
+ query.queryEnts(),
1076
+ ]);
1077
+ expect(ids.length).toBe(candidates.length);
1078
+ expect(count).toBe(candidates.length);
1079
+ expect(edges.length).toBe(candidates.length);
1080
+ expect(ents.length).toBe(candidates.length);
1081
+ expect(edges.map((e) => e.id2).sort()).toEqual(candidates.map((u) => u.id).sort());
1082
+ expect(ents.map((e) => e.id).sort()).toEqual(candidates.map((u) => u.id).sort());
1083
+ });
1084
+ test("multiple sources", async () => {
1085
+ const query = index_1.UserToFriendsQuery.query(user1.viewer, [
1086
+ user1.id,
1087
+ user2.id,
1088
+ ]).__intersect(index_1.UserToFriendsQuery.query(user2.viewer, users[2].id), index_1.UserToFriendsQuery.query(user2.viewer, users[3].id));
1089
+ // user0 => 0-9 - self
1090
+ // user1 => 0-8 - self
1091
+ // user2 => 0-7 - self
1092
+ // user3 => 0-6 -self
1093
+ // 7 users - 3 = 4 since you can't be friends with yourself
1094
+ // subtracting 3 for each instead of 4 like above because user1 not intersecting with user2
1095
+ const candidates1 = [users[1], users[4], users[5], users[6]];
1096
+ const candidates2 = [users[0], users[4], users[5], users[6]];
1097
+ const [idsMap, countMap, edgesMap, entsMap] = await Promise.all([
1098
+ query.queryAllIDs(),
1099
+ query.queryAllCount(),
1100
+ query.queryAllEdges(),
1101
+ query.queryAllEnts(),
1102
+ ]);
1103
+ async function verify(source, candidates) {
1104
+ const ids = idsMap.get(source.id) ?? [];
1105
+ const count = countMap.get(source.id) ?? [];
1106
+ const edges = edgesMap.get(source.id) ?? [];
1107
+ const ents = entsMap.get(source.id) ?? [];
1108
+ expect(ids.length).toBe(candidates.length);
1109
+ expect(count).toBe(candidates.length);
1110
+ expect(edges.length).toBe(candidates.length);
1111
+ expect(ents.length).toBe(candidates.length);
1112
+ expect(edges.map((e) => e.id2).sort()).toEqual(candidates.map((u) => u.id).sort());
1113
+ expect(ents.map((e) => e.id).sort()).toEqual(candidates.map((u) => u.id).sort());
1114
+ }
1115
+ await verify(user1, candidates1);
1116
+ await verify(user2, candidates2);
1117
+ });
1118
+ test("different edge types", async () => {
1119
+ const event = await (0, test_helpers_1.createTestEvent)(user2);
1120
+ for (let i = 0; i < 5; i++) {
1121
+ const newUser = await (0, test_helpers_1.createTestUser)();
1122
+ await (0, test_helpers_1.addEdge)(event, index_1.FakeEventSchema, index_1.EdgeType.EventToAttendees, false, newUser);
1123
+ }
1124
+ const query = index_1.UserToFriendsQuery.query(user1.viewer, user1.id).__intersect(index_1.EventToAttendeesQuery.query(user1.viewer, event.id));
1125
+ const count = await query.queryCount();
1126
+ expect(count).toBe(0);
1127
+ const candidates = [];
1128
+ for (let i = 0; i < users.length; i++) {
1129
+ const user = users[i];
1130
+ if (user.id !== user1.id && user.id !== user2.id) {
1131
+ await (0, test_helpers_1.addEdge)(event, index_1.FakeEventSchema, index_1.EdgeType.EventToAttendees, false, user);
1132
+ candidates.push(user);
1133
+ }
1134
+ }
1135
+ const query2 = index_1.UserToFriendsQuery.query(user1.viewer, user1.id).__intersect(index_1.EventToAttendeesQuery.query(user1.viewer, event.id));
1136
+ const count2 = await query2.queryCount();
1137
+ const ents = await query2.queryEnts();
1138
+ const ids = await query2.queryIDs();
1139
+ expect(count2).toBe(candidates.length);
1140
+ expect(ids.length).toBe(candidates.length);
1141
+ expect(ents.length).toBe(candidates.length);
1142
+ expect(ents.map((e) => e.id).sort()).toEqual(candidates.map((u) => u.id).sort());
1143
+ });
1144
+ });
1145
+ describe("union", () => {
1146
+ let users = [];
1147
+ let user1;
1148
+ let user2;
1149
+ let friends = new Map();
1150
+ beforeEach(async () => {
1151
+ users = [];
1152
+ friends = new Map();
1153
+ for (let i = 0; i < 10; i++) {
1154
+ const user = await (0, test_helpers_1.createTestUser)();
1155
+ users.push(user);
1156
+ }
1157
+ for (let i = 0; i < 10; i++) {
1158
+ const user = users[i];
1159
+ // decreasing number of friends for each user
1160
+ // we want little overlap so new users created.
1161
+ const candidates = users
1162
+ .slice(0, 10 - i)
1163
+ .filter((u) => u.id != user.id);
1164
+ // add at least 5 more to each user
1165
+ // every user should end up with 14 or 15
1166
+ // 9 + 5 = 14
1167
+ // 8 + 6 = 14
1168
+ // 7 + 7 = 14
1169
+ // 6 + 8 = 14
1170
+ // 5 + 9 = 14
1171
+ // 4 + 10 = 14
1172
+ // 3 + 11 = 14
1173
+ // 2 + 12 = 14
1174
+ // 1 + 13 = 14
1175
+ // 0 + 14 = 14
1176
+ // 10 original
1177
+ // 5 new for friend 1
1178
+ // 6 new for friend 2
1179
+ const newFriends = await Promise.all(new Array(5 + i).fill(null).map(() => (0, test_helpers_1.createTestUser)()));
1180
+ candidates.push(...newFriends);
1181
+ await (0, test_helpers_1.addEdge)(user, index_1.FakeUserSchema, index_1.EdgeType.UserToFriends, false, ...candidates);
1182
+ const count = await index_1.UserToFriendsQuery.query(user.viewer, user).queryRawCount();
1183
+ expect(count, `${i}`).toBe(candidates.length);
1184
+ friends.set(user.id, candidates);
1185
+ }
1186
+ user1 = users[0];
1187
+ user2 = users[1];
1188
+ });
1189
+ function getQuery() {
1190
+ return index_1.UserToFriendsQuery.query(user1.viewer, user1.id).__union(index_1.UserToFriendsQuery.query(user2.viewer, user2.id));
1191
+ }
1192
+ function getCandidateIDs() {
1193
+ const set = new Set();
1194
+ friends.get(user1.id)?.forEach((u) => set.add(u));
1195
+ friends.get(user2.id)?.forEach((u) => set.add(u));
1196
+ return Array.from(set.values()).map((u) => u.id);
1197
+ }
1198
+ test("ids", async () => {
1199
+ const ids = await getQuery().queryIDs();
1200
+ const candidates = getCandidateIDs();
1201
+ expect(ids.length).toBe(candidates.length);
1202
+ expect(ids.sort()).toEqual(candidates.sort());
1203
+ });
1204
+ test("count", async () => {
1205
+ const count = await getQuery().queryCount();
1206
+ const candidates = getCandidateIDs();
1207
+ expect(count).toBe(candidates.length);
1208
+ });
1209
+ test("raw_count", async () => {
1210
+ const count = await getQuery().queryRawCount();
1211
+ // raw count doesn't include the intersection
1212
+ expect(count).toBe(friends.get(user1.id)?.length);
1213
+ });
1214
+ test("edges", async () => {
1215
+ const edges = await getQuery().queryEdges();
1216
+ const candidates = getCandidateIDs();
1217
+ expect(edges.length).toBe(candidates.length);
1218
+ const idMap = new Map();
1219
+ for (const edge of edges) {
1220
+ const ct = (idMap.get(edge.id1) ?? 0) + 1;
1221
+ idMap.set(edge.id1, ct);
1222
+ }
1223
+ let id1count = friends.get(user1.id)?.length ?? 0;
1224
+ expect(idMap.get(user1.id)).toBe(id1count);
1225
+ expect(idMap.get(user2.id)).toBe(edges.length - id1count);
1226
+ });
1227
+ test("ents", async () => {
1228
+ // only returns privacy aware which is friends + self...
1229
+ const ents = await getQuery().queryEnts();
1230
+ const candidates = getCandidateIDs().sort();
1231
+ let id1count = friends.get(user1.id)?.length ?? 0;
1232
+ const visible = id1count + 1;
1233
+ expect(ents.length).toBe(visible);
1234
+ });
1235
+ test("first", async () => {
1236
+ const ents = await getQuery().first(2).queryEnts();
1237
+ expect(ents.length).toBe(2);
1238
+ });
1239
+ test("first. after each cursor", async () => {
1240
+ const edges = await getQuery().queryEdges();
1241
+ const { verify, getCursor } = (0, query_1.getVerifyAfterEachCursorGeneric)(edges, 3, user1, getQuery, ml, validateQueryIntersectOrUnion(ml, user1, user2));
1242
+ // hardcoded to test
1243
+ expect(edges.length).toBe(21);
1244
+ await verify(0, true, true, undefined);
1245
+ await verify(3, true, true, getCursor(edges[2]));
1246
+ await verify(6, true, true, getCursor(edges[5]));
1247
+ await verify(9, true, true, getCursor(edges[8]));
1248
+ await verify(12, true, true, getCursor(edges[11]));
1249
+ await verify(15, true, true, getCursor(edges[14]));
1250
+ await verify(18, true, true, getCursor(edges[17]));
1251
+ await verify(21, false, false, getCursor(edges[20]));
1252
+ });
1253
+ test("multiple unions", async () => {
1254
+ const user3 = users[2];
1255
+ const user4 = users[3];
1256
+ const query = index_1.UserToFriendsQuery.query(user1.viewer, user1.id).__union(index_1.UserToFriendsQuery.query(user1.viewer, user2.id), index_1.UserToFriendsQuery.query(user1.viewer, user3.id), index_1.UserToFriendsQuery.query(user1.viewer, user4.id));
1257
+ const candidates = Array.from(new Set([
1258
+ ...(friends.get(user1.id) ?? []),
1259
+ ...(friends.get(user2.id) ?? []),
1260
+ ...(friends.get(user3.id) ?? []),
1261
+ ...(friends.get(user4.id) ?? []), // 0-9 (-self) + 8 friends
1262
+ ]).values());
1263
+ // user1 can only see self + friends
1264
+ const user1Visible = friends.get(user1.id) ?? [];
1265
+ user1Visible.push(user1);
1266
+ // should be 36
1267
+ expect(candidates.length).toBe(10 + 5 + 6 + 7 + 8);
1268
+ const [ids, count, edges, ents] = await Promise.all([
1269
+ query.queryIDs(),
1270
+ query.queryCount(),
1271
+ query.queryEdges(),
1272
+ query.queryEnts(),
1273
+ ]);
1274
+ expect(ids.length).toBe(candidates.length);
1275
+ expect(count).toBe(candidates.length);
1276
+ expect(edges.length).toBe(candidates.length);
1277
+ expect(ents.length).toBe(user1Visible.length);
1278
+ expect(edges.map((e) => e.id2).sort()).toEqual(candidates.map((u) => u.id).sort());
1279
+ expect(ents.map((e) => e.id).sort()).toEqual(user1Visible.map((u) => u.id).sort());
1280
+ });
1281
+ test("multiple sources", async () => {
1282
+ const user3 = users[2];
1283
+ const user4 = users[3];
1284
+ const query = index_1.UserToFriendsQuery.query(user1.viewer, [
1285
+ user1.id,
1286
+ user2.id,
1287
+ ]).__union(index_1.UserToFriendsQuery.query(user2.viewer, user3.id), index_1.UserToFriendsQuery.query(user2.viewer, user4.id));
1288
+ const candidates1 = Array.from(new Set([
1289
+ ...(friends.get(user1.id) ?? []),
1290
+ ...(friends.get(user3.id) ?? []),
1291
+ ...(friends.get(user4.id) ?? []), // 0-9 (-self) + 8 friends
1292
+ ]).values());
1293
+ const candidates2 = Array.from(new Set([
1294
+ ...(friends.get(user2.id) ?? []),
1295
+ ...(friends.get(user3.id) ?? []),
1296
+ ...(friends.get(user4.id) ?? []), // 0-9 (-self) + 8 friends
1297
+ ]).values());
1298
+ const [idsMap, countMap, edgesMap, entsMap] = await Promise.all([
1299
+ query.queryAllIDs(),
1300
+ query.queryAllCount(),
1301
+ query.queryAllEdges(),
1302
+ query.queryAllEnts(),
1303
+ ]);
1304
+ // user1 can only see self + friends
1305
+ const user1Visible = friends.get(user1.id) ?? [];
1306
+ user1Visible.push(user1);
1307
+ // since the EntQuery uses user1's viewer
1308
+ // can only see user2's friends that intersect with user1's friends
1309
+ // and that's every user except for the last user since we don't add that user to user2's friends
1310
+ const user2Visible = users.slice(0, users.length - 1);
1311
+ async function verify(source, candidates, visible) {
1312
+ const ids = idsMap.get(source.id) ?? [];
1313
+ const count = countMap.get(source.id) ?? [];
1314
+ const edges = edgesMap.get(source.id) ?? [];
1315
+ const ents = entsMap.get(source.id) ?? [];
1316
+ expect(ids.length).toBe(candidates.length);
1317
+ expect(count).toBe(candidates.length);
1318
+ expect(edges.length).toBe(candidates.length);
1319
+ expect(ents.length).toBe(visible.length);
1320
+ expect(edges.map((e) => e.id2).sort()).toEqual(candidates.map((u) => u.id).sort());
1321
+ expect(ents.map((e) => e.id).sort()).toEqual(visible.map((u) => u.id).sort());
1322
+ }
1323
+ await verify(user1, candidates1, user1Visible);
1324
+ await verify(user2, candidates2, user2Visible);
1325
+ });
1326
+ test("different edge types", async () => {
1327
+ const event = await (0, test_helpers_1.createTestEvent)(user2);
1328
+ const newUsers = [];
1329
+ for (let i = 0; i < 5; i++) {
1330
+ const newUser = await (0, test_helpers_1.createTestUser)();
1331
+ await (0, test_helpers_1.addEdge)(event, index_1.FakeEventSchema, index_1.EdgeType.EventToAttendees, false, newUser);
1332
+ newUsers.push(newUser);
1333
+ }
1334
+ const query = index_1.UserToFriendsQuery.query(user1.viewer, user1.id).__union(index_1.EventToAttendeesQuery.query(user1.viewer, event.id));
1335
+ const candidates = [
1336
+ ...(friends.get(user1.id) ?? []).map((u) => u.id),
1337
+ ...newUsers.map((u) => u.id),
1338
+ ];
1339
+ const count = await query.queryCount();
1340
+ expect(count).toBe(candidates.length);
1341
+ const ents = await query.queryEnts();
1342
+ const ids = await query.queryIDs();
1343
+ expect(ids.length).toBe(candidates.length);
1344
+ // can only see friends
1345
+ expect(ents.length).toBe(friends.get(user1.id)?.length);
1346
+ });
1347
+ });
951
1348
  if (!global) {
952
1349
  return;
953
1350
  }
@@ -14,13 +14,7 @@ const builder_1 = require("../../testutils/builder");
14
14
  const action_1 = require("../../action");
15
15
  const clause_1 = require("../clause");
16
16
  const query_impl_1 = require("../query_impl");
17
- function getWhereClause(query) {
18
- const idx = query.query.indexOf("WHERE");
19
- if (idx !== -1) {
20
- return query.query.substr(idx + 6);
21
- }
22
- return null;
23
- }
17
+ const query_1 = require("../../testutils/query");
24
18
  const commonTests = (opts) => {
25
19
  (0, logger_1.setLogLevels)(["query", "error"]);
26
20
  const ml = opts.ml;
@@ -152,7 +146,7 @@ const commonTests = (opts) => {
152
146
  const uniqCol = isCustomQuery(filter) ? "id" : "id2";
153
147
  expect(ml.logs.length).toBe(length);
154
148
  for (let i = 0; i < numQueries; i++) {
155
- const whereClause = getWhereClause(ml.logs[i]);
149
+ const whereClause = (0, query_1.getWhereClause)(ml.logs[i]);
156
150
  let expLimit = disablePaginationBump ? limit : limit + 1;
157
151
  expect(whereClause, `${i}`).toBe(
158
152
  // default limit
@@ -162,7 +156,7 @@ const commonTests = (opts) => {
162
156
  function verifyCountQuery({ length = 1, numQueries = 1 }) {
163
157
  expect(ml.logs.length).toBe(length);
164
158
  for (let i = 0; i < numQueries; i++) {
165
- const whereClause = getWhereClause(ml.logs[i]);
159
+ const whereClause = (0, query_1.getWhereClause)(ml.logs[i]);
166
160
  expect(whereClause).toBe(opts.clause.clause(1));
167
161
  }
168
162
  }
@@ -180,7 +174,7 @@ const commonTests = (opts) => {
180
174
  else {
181
175
  parts.push(cmp);
182
176
  }
183
- expect(getWhereClause(ml.logs[0])).toBe(`${parts.join(" AND ")} ORDER BY ${(0, query_impl_1.getOrderByPhrase)(opts.orderby)}, ${uniqCol} ${opts.orderby[0].direction} LIMIT ${limit + 1}`);
177
+ expect((0, query_1.getWhereClause)(ml.logs[0])).toBe(`${parts.join(" AND ")} ORDER BY ${(0, query_impl_1.getOrderByPhrase)(opts.orderby)}, ${uniqCol} ${opts.orderby[0].direction} LIMIT ${limit + 1}`);
184
178
  }
185
179
  function verifyLastBeforeCursorQuery(filter, { length = 1, limit = 3, orderby = opts.orderby }) {
186
180
  // cache showing up in a few because of cross runs...
@@ -196,7 +190,7 @@ const commonTests = (opts) => {
196
190
  else {
197
191
  parts.push(cmp);
198
192
  }
199
- expect(getWhereClause(ml.logs[0])).toBe(
193
+ expect((0, query_1.getWhereClause)(ml.logs[0])).toBe(
200
194
  // extra fetched for pagination
201
195
  `${parts.join(" AND ")} ORDER BY ${(0, query_impl_1.getOrderByPhrase)(orderby)}, ${uniqCol} ${orderby[0].direction} LIMIT ${limit + 1}`);
202
196
  }
@@ -210,38 +204,16 @@ const commonTests = (opts) => {
210
204
  });
211
205
  }
212
206
  function getVerifyAfterEachCursor(edges, pageLength, user) {
213
- let query;
214
- async function verify(i, hasEdge, hasNextPage, cursor) {
215
- ml.clear();
216
- query = opts.newQuery(getViewer(), user);
217
- const newEdges = await query.first(pageLength, cursor).queryEdges();
218
- const pagination = query.paginationInfo().get(user.id);
219
- if (hasEdge) {
220
- expect(newEdges[0], `${i}`).toStrictEqual(edges[i]);
221
- expect(newEdges.length, `${i}`).toBe(edges.length - i >= pageLength ? pageLength : edges.length - i);
222
- }
223
- else {
224
- expect(newEdges.length, `${i}`).toBe(0);
225
- }
226
- if (hasNextPage) {
227
- expect(pagination?.hasNextPage, `${i}`).toBe(true);
228
- expect(pagination?.hasPreviousPage, `${i}`).toBe(false);
229
- }
230
- else {
231
- expect(pagination?.hasNextPage, `${i}`).toBe(undefined);
232
- expect(pagination?.hasNextPage, `${i}`).toBe(undefined);
233
- }
207
+ return (0, query_1.getVerifyAfterEachCursorGeneric)(edges, pageLength, user,
208
+ // @ts-ignore weirdness with import paths...
209
+ () => opts.newQuery(getViewer(), user), ml, (query, cursor) => {
234
210
  if (cursor) {
235
211
  verifyFirstAfterCursorQuery(query, 1, pageLength);
236
212
  }
237
213
  else {
238
214
  verifyQuery(query, { orderby: opts.orderby, limit: pageLength });
239
215
  }
240
- }
241
- function getCursor(edge) {
242
- return query.getCursor(edge);
243
- }
244
- return { verify, getCursor };
216
+ });
245
217
  }
246
218
  function getVerifyBeforeEachCursor(edges, pageLength, user) {
247
219
  let query;
@@ -5,6 +5,7 @@ export interface OrderByOption {
5
5
  nullsPlacement?: "first" | "last";
6
6
  }
7
7
  export type OrderBy = OrderByOption[];
8
- export declare function getOrderByPhrase(orderby: OrderBy): string;
8
+ export declare function getOrderByPhrase(orderby: OrderBy, alias?: string): string;
9
9
  export declare function reverseOrderBy(orderby: OrderBy): OrderBy;
10
+ export declare function getJoinPhrase(join: NonNullable<QueryableDataOptions["join"]>, clauseIdx?: number): string;
10
11
  export declare function buildQuery(options: QueryableDataOptions): string;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildQuery = exports.reverseOrderBy = exports.getOrderByPhrase = void 0;
4
- function getOrderByPhrase(orderby) {
3
+ exports.buildQuery = exports.getJoinPhrase = exports.reverseOrderBy = exports.getOrderByPhrase = void 0;
4
+ function getOrderByPhrase(orderby, alias) {
5
5
  return orderby
6
6
  .map((v) => {
7
7
  let nullsPlacement = "";
@@ -13,7 +13,8 @@ function getOrderByPhrase(orderby) {
13
13
  nullsPlacement = " NULLS LAST";
14
14
  break;
15
15
  }
16
- return `${v.column} ${v.direction}${nullsPlacement}`;
16
+ const col = alias ? `${alias}.${v.column}` : v.column;
17
+ return `${col} ${v.direction}${nullsPlacement}`;
17
18
  })
18
19
  .join(", ");
19
20
  }
@@ -26,17 +27,34 @@ function reverseOrderBy(orderby) {
26
27
  });
27
28
  }
28
29
  exports.reverseOrderBy = reverseOrderBy;
30
+ function getJoinPhrase(join, clauseIdx = 1) {
31
+ const joinTable = join.alias
32
+ ? `${join.tableName} ${join.alias}`
33
+ : join.tableName;
34
+ return `${joinTable} ON ${join.clause.clause(clauseIdx)}`;
35
+ }
36
+ exports.getJoinPhrase = getJoinPhrase;
29
37
  function buildQuery(options) {
30
- const fields = options.fields.join(", ");
38
+ const fields = options.alias
39
+ ? options.fields.map((f) => `${options.alias}.${f}`).join(", ")
40
+ : options.fields.join(", ");
31
41
  // always start at 1
32
- const whereClause = options.clause.clause(1);
33
42
  const parts = [];
34
- parts.push(`SELECT ${fields} FROM ${options.tableName} WHERE ${whereClause}`);
43
+ const tableName = options.alias
44
+ ? `${options.tableName} AS ${options.alias}`
45
+ : options.tableName;
46
+ parts.push(`SELECT ${fields} FROM ${tableName}`);
47
+ let whereStart = 1;
48
+ if (options.join) {
49
+ parts.push(`JOIN ${getJoinPhrase(options.join, 1)}`);
50
+ whereStart += options.join.clause.values().length;
51
+ }
52
+ parts.push(`WHERE ${options.clause.clause(whereStart, options.alias)}`);
35
53
  if (options.groupby) {
36
54
  parts.push(`GROUP BY ${options.groupby}`);
37
55
  }
38
56
  if (options.orderby) {
39
- parts.push(`ORDER BY ${getOrderByPhrase(options.orderby)}`);
57
+ parts.push(`ORDER BY ${getOrderByPhrase(options.orderby, options.alias)}`);
40
58
  }
41
59
  if (options.limit) {
42
60
  parts.push(`LIMIT ${options.limit}`);
@@ -49,7 +49,7 @@ class GraphQLEdgeConnection {
49
49
  this.query = fn(this.query);
50
50
  }
51
51
  async queryTotalCount() {
52
- return await this.query.queryRawCount();
52
+ return this.query.queryRawCount();
53
53
  }
54
54
  async queryEdges() {
55
55
  // because of privacy, we need to query the node regardless of if the node is there
@@ -60,7 +60,7 @@ class GraphQLEdgeConnection {
60
60
  // if nodes queried just return ents
61
61
  // unlikely to query nodes and pageInfo so we just load this separately for now
62
62
  async queryNodes() {
63
- return await this.query.queryEnts();
63
+ return this.query.queryEnts();
64
64
  }
65
65
  defaultPageInfo() {
66
66
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowtop/ent",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "snowtop ent framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "minimist": "^1.2.8",
22
22
  "pascal-case": "^3.1.2",
23
23
  "pg": "^8.11.0",
24
- "prettier": "^2.8.8",
24
+ "prettier": "^3.0.3",
25
25
  "snake-case": "^3.0.4",
26
26
  "ts-node": "^10.9.1",
27
27
  "tsconfig-paths": "^4.2.0",