@snowtop/ent 0.1.0-alpha15 → 0.1.0-alpha150

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 (170) hide show
  1. package/action/action.d.ts +27 -16
  2. package/action/action.js +22 -7
  3. package/action/executor.d.ts +16 -3
  4. package/action/executor.js +90 -23
  5. package/action/experimental_action.d.ts +25 -16
  6. package/action/experimental_action.js +35 -9
  7. package/action/index.d.ts +3 -1
  8. package/action/index.js +7 -1
  9. package/action/operations.d.ts +125 -0
  10. package/action/operations.js +684 -0
  11. package/action/orchestrator.d.ts +38 -12
  12. package/action/orchestrator.js +427 -102
  13. package/action/relative_value.d.ts +47 -0
  14. package/action/relative_value.js +125 -0
  15. package/action/transaction.d.ts +10 -0
  16. package/action/transaction.js +23 -0
  17. package/auth/auth.d.ts +1 -1
  18. package/core/base.d.ts +51 -21
  19. package/core/base.js +7 -1
  20. package/core/clause.d.ts +85 -40
  21. package/core/clause.js +375 -64
  22. package/core/config.d.ts +15 -1
  23. package/core/config.js +10 -1
  24. package/core/const.d.ts +3 -0
  25. package/core/const.js +6 -0
  26. package/core/context.d.ts +4 -2
  27. package/core/context.js +20 -2
  28. package/core/convert.d.ts +1 -1
  29. package/core/date.js +1 -5
  30. package/core/db.d.ts +12 -8
  31. package/core/db.js +18 -8
  32. package/core/ent.d.ts +68 -94
  33. package/core/ent.js +535 -586
  34. package/core/global_schema.d.ts +7 -0
  35. package/core/global_schema.js +51 -0
  36. package/core/loaders/assoc_count_loader.d.ts +1 -0
  37. package/core/loaders/assoc_count_loader.js +10 -2
  38. package/core/loaders/assoc_edge_loader.d.ts +1 -1
  39. package/core/loaders/assoc_edge_loader.js +10 -13
  40. package/core/loaders/index.d.ts +1 -1
  41. package/core/loaders/index.js +1 -3
  42. package/core/loaders/index_loader.d.ts +3 -3
  43. package/core/loaders/loader.d.ts +2 -2
  44. package/core/loaders/loader.js +5 -5
  45. package/core/loaders/object_loader.d.ts +30 -9
  46. package/core/loaders/object_loader.js +225 -78
  47. package/core/loaders/query_loader.d.ts +6 -12
  48. package/core/loaders/query_loader.js +54 -13
  49. package/core/loaders/raw_count_loader.js +5 -1
  50. package/core/logger.d.ts +1 -1
  51. package/core/logger.js +1 -0
  52. package/core/privacy.d.ts +7 -6
  53. package/core/privacy.js +21 -25
  54. package/core/query/assoc_query.d.ts +3 -2
  55. package/core/query/assoc_query.js +9 -1
  56. package/core/query/custom_clause_query.d.ts +27 -0
  57. package/core/query/custom_clause_query.js +88 -0
  58. package/core/query/custom_query.d.ts +17 -2
  59. package/core/query/custom_query.js +88 -13
  60. package/core/query/index.d.ts +1 -0
  61. package/core/query/index.js +3 -1
  62. package/core/query/query.d.ts +15 -3
  63. package/core/query/query.js +128 -53
  64. package/core/query/shared_assoc_test.d.ts +2 -1
  65. package/core/query/shared_assoc_test.js +44 -54
  66. package/core/query/shared_test.d.ts +8 -1
  67. package/core/query/shared_test.js +532 -236
  68. package/core/viewer.d.ts +2 -0
  69. package/core/viewer.js +3 -1
  70. package/graphql/graphql.d.ts +52 -19
  71. package/graphql/graphql.js +174 -136
  72. package/graphql/graphql_field_helpers.d.ts +7 -1
  73. package/graphql/graphql_field_helpers.js +21 -1
  74. package/graphql/index.d.ts +2 -2
  75. package/graphql/index.js +3 -5
  76. package/graphql/query/connection_type.d.ts +9 -9
  77. package/graphql/query/shared_assoc_test.js +1 -1
  78. package/graphql/query/shared_edge_connection.js +1 -19
  79. package/graphql/scalars/orderby_direction.d.ts +2 -0
  80. package/graphql/scalars/orderby_direction.js +15 -0
  81. package/imports/dataz/example1/_auth.js +128 -47
  82. package/imports/dataz/example1/_viewer.js +87 -39
  83. package/imports/index.d.ts +7 -2
  84. package/imports/index.js +20 -5
  85. package/index.d.ts +14 -5
  86. package/index.js +26 -10
  87. package/package.json +18 -17
  88. package/parse_schema/parse.d.ts +31 -9
  89. package/parse_schema/parse.js +179 -32
  90. package/schema/base_schema.d.ts +13 -3
  91. package/schema/base_schema.js +13 -0
  92. package/schema/field.d.ts +78 -21
  93. package/schema/field.js +231 -71
  94. package/schema/index.d.ts +2 -2
  95. package/schema/index.js +7 -2
  96. package/schema/json_field.d.ts +16 -4
  97. package/schema/json_field.js +32 -2
  98. package/schema/schema.d.ts +109 -20
  99. package/schema/schema.js +42 -53
  100. package/schema/struct_field.d.ts +15 -3
  101. package/schema/struct_field.js +117 -22
  102. package/schema/union_field.d.ts +1 -1
  103. package/scripts/custom_compiler.js +12 -8
  104. package/scripts/custom_graphql.js +145 -34
  105. package/scripts/migrate_v0.1.js +36 -0
  106. package/scripts/move_types.js +120 -0
  107. package/scripts/read_schema.js +22 -7
  108. package/testutils/action/complex_schemas.d.ts +69 -0
  109. package/testutils/action/complex_schemas.js +405 -0
  110. package/testutils/builder.d.ts +39 -43
  111. package/testutils/builder.js +75 -49
  112. package/testutils/db/fixture.d.ts +10 -0
  113. package/testutils/db/fixture.js +26 -0
  114. package/testutils/db/{test_db.d.ts → temp_db.d.ts} +32 -8
  115. package/testutils/db/{test_db.js → temp_db.js} +244 -48
  116. package/testutils/db/value.d.ts +7 -0
  117. package/testutils/db/value.js +251 -0
  118. package/testutils/db_mock.d.ts +16 -4
  119. package/testutils/db_mock.js +52 -9
  120. package/testutils/db_time_zone.d.ts +4 -0
  121. package/testutils/db_time_zone.js +41 -0
  122. package/testutils/ent-graphql-tests/index.d.ts +7 -1
  123. package/testutils/ent-graphql-tests/index.js +56 -26
  124. package/testutils/fake_comms.js +1 -1
  125. package/testutils/fake_data/const.d.ts +2 -1
  126. package/testutils/fake_data/const.js +3 -0
  127. package/testutils/fake_data/fake_contact.d.ts +7 -3
  128. package/testutils/fake_data/fake_contact.js +13 -7
  129. package/testutils/fake_data/fake_event.d.ts +4 -1
  130. package/testutils/fake_data/fake_event.js +7 -6
  131. package/testutils/fake_data/fake_tag.d.ts +36 -0
  132. package/testutils/fake_data/fake_tag.js +89 -0
  133. package/testutils/fake_data/fake_user.d.ts +8 -5
  134. package/testutils/fake_data/fake_user.js +16 -15
  135. package/testutils/fake_data/index.js +5 -1
  136. package/testutils/fake_data/internal.d.ts +2 -0
  137. package/testutils/fake_data/internal.js +7 -1
  138. package/testutils/fake_data/tag_query.d.ts +13 -0
  139. package/testutils/fake_data/tag_query.js +43 -0
  140. package/testutils/fake_data/test_helpers.d.ts +11 -4
  141. package/testutils/fake_data/test_helpers.js +29 -13
  142. package/testutils/fake_data/user_query.d.ts +11 -4
  143. package/testutils/fake_data/user_query.js +54 -22
  144. package/testutils/fake_log.js +1 -1
  145. package/testutils/parse_sql.d.ts +6 -0
  146. package/testutils/parse_sql.js +16 -2
  147. package/testutils/test_edge_global_schema.d.ts +15 -0
  148. package/testutils/test_edge_global_schema.js +62 -0
  149. package/testutils/write.d.ts +2 -2
  150. package/testutils/write.js +33 -7
  151. package/tsc/ast.d.ts +25 -2
  152. package/tsc/ast.js +141 -17
  153. package/tsc/compilerOptions.js +5 -1
  154. package/tsc/move_generated.d.ts +1 -0
  155. package/tsc/move_generated.js +164 -0
  156. package/tsc/transform.d.ts +22 -0
  157. package/tsc/transform.js +182 -0
  158. package/tsc/transform_action.d.ts +22 -0
  159. package/tsc/transform_action.js +183 -0
  160. package/tsc/transform_ent.d.ts +17 -0
  161. package/tsc/transform_ent.js +60 -0
  162. package/tsc/transform_schema.d.ts +27 -0
  163. package/{scripts → tsc}/transform_schema.js +146 -117
  164. package/graphql/enums.d.ts +0 -3
  165. package/graphql/enums.js +0 -25
  166. package/scripts/move_generated.js +0 -142
  167. package/scripts/transform_code.js +0 -113
  168. package/scripts/transform_schema.d.ts +0 -1
  169. /package/scripts/{move_generated.d.ts → migrate_v0.1.d.ts} +0 -0
  170. /package/scripts/{transform_code.d.ts → move_types.d.ts} +0 -0
package/core/ent.js CHANGED
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -22,13 +26,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
27
  };
24
28
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.getEdgeTypeInGroup = exports.applyPrivacyPolicyForRows = exports.applyPrivacyPolicyForRowX = exports.applyPrivacyPolicyForRow = exports.loadNodesByEdge = exports.loadEdgeForID2 = exports.loadRawEdgeCountX = exports.loadUniqueNode = exports.loadUniqueEdge = exports.loadCustomEdges = exports.loadEdges = exports.defaultEdgeQueryOptions = exports.DefaultLimit = exports.loadEdgeDatas = exports.loadEdgeData = exports.assocEdgeLoader = exports.AssocEdgeData = exports.getCursor = exports.AssocEdge = exports.DeleteNodeOperation = exports.deleteRowsSync = exports.deleteRows = exports.editRowSync = exports.editRow = exports.buildUpdateQuery = exports.createRowSync = exports.createRow = exports.buildInsertQuery = exports.EdgeOperation = exports.EditNodeOperation = exports.buildGroupQuery = exports.buildQuery = exports.loadRows = exports.performRawQuery = exports.loadRow = exports.loadRowX = exports.loadDerivedEntX = exports.loadDerivedEnt = exports.loadCustomData = exports.loadCustomEnts = exports.loadEntsFromClause = exports.loadEntsList = exports.loadEnts = exports.loadEntXFromClause = exports.loadEntFromClause = exports.loadEntXViaKey = exports.loadEntX = exports.loadEntViaKey = exports.loadEnt = void 0;
29
+ exports.getEdgeTypeInGroup = exports.applyPrivacyPolicyForRows = exports.applyPrivacyPolicyForRow = exports.loadNodesByEdge = exports.loadEdgeForID2 = exports.loadRawEdgeCountX = exports.loadUniqueNode = exports.loadUniqueEdge = exports.loadCustomEdges = exports.getEdgeClauseAndFields = exports.loadEdges = exports.getDefaultLimit = exports.setDefaultLimit = exports.loadEdgeDatas = exports.loadEdgeData = exports.assocEdgeLoader = exports.AssocEdgeData = exports.getCursor = exports.AssocEdge = exports.deleteRowsSync = exports.deleteRows = exports.editRowSync = exports.editRow = exports.buildUpdateQuery = exports.createRowSync = exports.createRow = exports.buildInsertQuery = exports.buildGroupQuery = exports.buildQuery = exports.loadRows = exports.performRawQuery = exports.___setLogQueryErrorWithError = exports.loadRow = exports.loadRowX = exports.logQuery = exports.loadDerivedEntX = exports.loadDerivedEnt = exports.loadCustomCount = exports.loadCustomData = exports.loadCustomEnts = exports.loadEntsFromClause = exports.loadEntsList = exports.loadEnts = exports.loadEntXFromClause = exports.loadEntFromClause = exports.loadEntXViaKey = exports.loadEntX = exports.loadEntViaKey = exports.loadEnt = exports.getEntKey = void 0;
26
30
  const db_1 = __importStar(require("./db"));
27
31
  const privacy_1 = require("./privacy");
28
32
  const clause = __importStar(require("./clause"));
29
- const action_1 = require("../action");
30
33
  const logger_1 = require("./logger");
31
34
  const dataloader_1 = __importDefault(require("dataloader"));
35
+ const global_schema_1 = require("./global_schema");
32
36
  // TODO kill this and createDataLoader
33
37
  class cacheMap {
34
38
  constructor(options) {
@@ -38,7 +42,7 @@ class cacheMap {
38
42
  get(key) {
39
43
  const ret = this.m.get(key);
40
44
  if (ret) {
41
- (0, logger_1.log)("query", {
45
+ (0, logger_1.log)("cache", {
42
46
  "dataloader-cache-hit": key,
43
47
  "tableName": this.options.tableName,
44
48
  });
@@ -55,12 +59,41 @@ class cacheMap {
55
59
  return this.m.clear();
56
60
  }
57
61
  }
62
+ class entCacheMap {
63
+ constructor(viewer, options) {
64
+ this.viewer = viewer;
65
+ this.options = options;
66
+ this.m = new Map();
67
+ this.logEnabled = false;
68
+ this.logEnabled = (0, logger_1.logEnabled)("cache");
69
+ }
70
+ get(id) {
71
+ const ret = this.m.get(id);
72
+ if (this.logEnabled && ret) {
73
+ const key = getEntKey(this.viewer, id, this.options);
74
+ (0, logger_1.log)("cache", {
75
+ "ent-cache-hit": key,
76
+ });
77
+ }
78
+ return ret;
79
+ }
80
+ set(key, value) {
81
+ return this.m.set(key, value);
82
+ }
83
+ delete(key) {
84
+ return this.m.delete(key);
85
+ }
86
+ clear() {
87
+ return this.m.clear();
88
+ }
89
+ }
58
90
  function createDataLoader(options) {
59
91
  const loaderOptions = {};
60
92
  // if query logging is enabled, we should log what's happening with loader
61
93
  if ((0, logger_1.logEnabled)("query")) {
62
94
  loaderOptions.cacheMap = new cacheMap(options);
63
95
  }
96
+ // something here brokwn with strict:true
64
97
  return new dataloader_1.default(async (ids) => {
65
98
  if (!ids.length) {
66
99
  return [];
@@ -84,28 +117,151 @@ function createDataLoader(options) {
84
117
  return result;
85
118
  }, loaderOptions);
86
119
  }
87
- // Ent accessors
120
+ // used to wrap errors that would eventually be thrown in ents
121
+ // not an Error because DataLoader automatically rejects that
122
+ class ErrorWrapper {
123
+ constructor(error) {
124
+ this.error = error;
125
+ }
126
+ }
127
+ function createEntLoader(viewer, options, map) {
128
+ // share the cache across loaders even if we create a new instance
129
+ const loaderOptions = {};
130
+ loaderOptions.cacheMap = map;
131
+ return new dataloader_1.default(async (ids) => {
132
+ if (!ids.length) {
133
+ return [];
134
+ }
135
+ let result = [];
136
+ const tableName = options.loaderFactory.options?.tableName;
137
+ const loader = options.loaderFactory.createLoader(viewer.context);
138
+ const rows = await loader.loadMany(ids);
139
+ // this is a loader which should return the same order based on passed-in ids
140
+ // so let's depend on that...
141
+ for (let idx = 0; idx < rows.length; idx++) {
142
+ const row = rows[idx];
143
+ // db error
144
+ if (row instanceof Error) {
145
+ result[idx] = row;
146
+ continue;
147
+ }
148
+ else if (!row) {
149
+ if (tableName) {
150
+ result[idx] = new ErrorWrapper(new Error(`couldn't find row for value ${ids[idx]} in table ${tableName}`));
151
+ }
152
+ else {
153
+ result[idx] = new ErrorWrapper(new Error(`couldn't find row for value ${ids[idx]}`));
154
+ }
155
+ }
156
+ else {
157
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
158
+ if (r instanceof Error) {
159
+ result[idx] = new ErrorWrapper(r);
160
+ }
161
+ else {
162
+ result[idx] = r;
163
+ }
164
+ }
165
+ }
166
+ return result;
167
+ }, loaderOptions);
168
+ }
169
+ class EntLoader {
170
+ constructor(viewer, options) {
171
+ this.viewer = viewer;
172
+ this.options = options;
173
+ this.map = new entCacheMap(viewer, options);
174
+ this.loader = createEntLoader(this.viewer, this.options, this.map);
175
+ }
176
+ getMap() {
177
+ return this.map;
178
+ }
179
+ async load(id) {
180
+ return this.loader.load(id);
181
+ }
182
+ async loadMany(ids) {
183
+ return this.loader.loadMany(ids);
184
+ }
185
+ prime(id, ent) {
186
+ this.loader.prime(id, ent);
187
+ }
188
+ clear(id) {
189
+ this.loader.clear(id);
190
+ }
191
+ clearAll() {
192
+ this.loader.clearAll();
193
+ }
194
+ }
195
+ function getEntLoader(viewer, options) {
196
+ if (!viewer.context?.cache) {
197
+ return new EntLoader(viewer, options);
198
+ }
199
+ const name = `ent-loader:${viewer.instanceKey()}:${options.loaderFactory.name}`;
200
+ return viewer.context.cache.getLoaderWithLoadMany(name, () => new EntLoader(viewer, options));
201
+ }
202
+ function getEntKey(viewer, id, options) {
203
+ return `${viewer.instanceKey()}:${options.loaderFactory.name}:${id}`;
204
+ }
205
+ exports.getEntKey = getEntKey;
88
206
  async function loadEnt(viewer, id, options) {
89
- const row = await options.loaderFactory.createLoader(viewer.context).load(id);
90
- return await applyPrivacyPolicyForRow(viewer, options, row);
207
+ if (typeof id !== "string" &&
208
+ typeof id !== "number" &&
209
+ typeof id !== "bigint") {
210
+ throw new Error(`invalid id ${id} passed to loadEnt`);
211
+ }
212
+ const r = await getEntLoader(viewer, options).load(id);
213
+ return r instanceof ErrorWrapper ? null : r;
91
214
  }
92
215
  exports.loadEnt = loadEnt;
216
+ async function applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options,
217
+ // can pass in loader when calling this for multi-id cases...
218
+ loader) {
219
+ if (!loader) {
220
+ loader = getEntLoader(viewer, options);
221
+ }
222
+ // TODO every row.id needs to be audited...
223
+ // https://github.com/lolopinto/ent/issues/1064
224
+ const id = row.id;
225
+ // we should check the ent loader cache to see if this is already there
226
+ // TODO hmm... we eventually need a custom data-loader for this too so that it's all done correctly if there's a complicated fetch deep down in graphql
227
+ const result = loader.getMap().get(id);
228
+ if (result !== undefined) {
229
+ return result;
230
+ }
231
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
232
+ if (r instanceof Error) {
233
+ loader.prime(id, new ErrorWrapper(r));
234
+ return new ErrorWrapper(r);
235
+ }
236
+ else {
237
+ loader.prime(id, r);
238
+ return r;
239
+ }
240
+ }
93
241
  // this is the same implementation-wise (right now) as loadEnt. it's just clearer that it's not loaded via ID.
94
242
  // used for load via email address etc
95
243
  async function loadEntViaKey(viewer, key, options) {
96
244
  const row = await options.loaderFactory
97
245
  .createLoader(viewer.context)
98
246
  .load(key);
99
- return await applyPrivacyPolicyForRow(viewer, options, row);
247
+ if (!row) {
248
+ return null;
249
+ }
250
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options);
251
+ return r instanceof ErrorWrapper ? null : r;
100
252
  }
101
253
  exports.loadEntViaKey = loadEntViaKey;
102
254
  async function loadEntX(viewer, id, options) {
103
- const row = await options.loaderFactory.createLoader(viewer.context).load(id);
104
- if (!row) {
105
- // todo make this better
106
- throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${id}`);
255
+ if (typeof id !== "string" &&
256
+ typeof id !== "number" &&
257
+ typeof id !== "bigint") {
258
+ throw new Error(`invalid id ${id} passed to loadEntX`);
107
259
  }
108
- return await applyPrivacyPolicyForRowX(viewer, options, row);
260
+ const r = await getEntLoader(viewer, options).load(id);
261
+ if (r instanceof ErrorWrapper) {
262
+ throw r.error;
263
+ }
264
+ return r;
109
265
  }
110
266
  exports.loadEntX = loadEntX;
111
267
  async function loadEntXViaKey(viewer, key, options) {
@@ -116,9 +272,16 @@ async function loadEntXViaKey(viewer, key, options) {
116
272
  // todo make this better
117
273
  throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${key}`);
118
274
  }
119
- return await applyPrivacyPolicyForRowX(viewer, options, row);
275
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options);
276
+ if (r instanceof ErrorWrapper) {
277
+ throw r.error;
278
+ }
279
+ return r;
120
280
  }
121
281
  exports.loadEntXViaKey = loadEntXViaKey;
282
+ /**
283
+ * @deprecated use loadCustomEnts
284
+ */
122
285
  async function loadEntFromClause(viewer, options, clause) {
123
286
  const rowOptions = {
124
287
  ...options,
@@ -126,12 +289,18 @@ async function loadEntFromClause(viewer, options, clause) {
126
289
  context: viewer.context,
127
290
  };
128
291
  const row = await loadRow(rowOptions);
129
- return await applyPrivacyPolicyForRow(viewer, options, row);
292
+ if (row === null) {
293
+ return null;
294
+ }
295
+ return applyPrivacyPolicyForRow(viewer, options, row);
130
296
  }
131
297
  exports.loadEntFromClause = loadEntFromClause;
132
298
  // same as loadEntFromClause
133
299
  // only works for ents where primary key is "id"
134
300
  // use loadEnt with a loaderFactory if different
301
+ /**
302
+ * @deprecated use loadCustomEnts
303
+ */
135
304
  async function loadEntXFromClause(viewer, options, clause) {
136
305
  const rowOptions = {
137
306
  ...options,
@@ -146,37 +315,19 @@ async function loadEnts(viewer, options, ...ids) {
146
315
  if (!ids.length) {
147
316
  return new Map();
148
317
  }
149
- let loaded = false;
150
- let rows = [];
151
- // TODO loadMany everywhere
152
- const l = options.loaderFactory.createLoader(viewer.context);
153
- if (l.loadMany) {
154
- loaded = true;
155
- rows = await l.loadMany(ids);
156
- }
157
- // TODO rewrite all of this
318
+ // result
158
319
  let m = new Map();
159
- if (loaded) {
160
- let rows2 = [];
161
- for (const row of rows) {
162
- if (!row) {
163
- continue;
164
- }
165
- if (row instanceof Error) {
166
- throw row;
167
- }
168
- rows2.push(row);
320
+ const ret = await getEntLoader(viewer, options).loadMany(ids);
321
+ for (const r of ret) {
322
+ if (r instanceof Error) {
323
+ throw r;
169
324
  }
170
- m = await applyPrivacyPolicyForRows(viewer, rows2, options);
171
- }
172
- else {
173
- m = await loadEntsFromClause(viewer,
174
- // this is always "id" if not using an ObjectLoaderFactory
175
- clause.In("id", ...ids), options);
325
+ if (r instanceof ErrorWrapper) {
326
+ continue;
327
+ }
328
+ m.set(r.id, r);
176
329
  }
177
330
  return m;
178
- // TODO do we want to change this to be a map not a list so that it's easy to check for existence?
179
- // TODO eventually this should be doing a cache then db queyr and maybe depend on dataloader to get all the results at once
180
331
  }
181
332
  exports.loadEnts = loadEnts;
182
333
  // calls loadEnts and returns the results sorted in the order they were passed in
@@ -195,6 +346,9 @@ async function loadEntsList(viewer, options, ...ids) {
195
346
  exports.loadEntsList = loadEntsList;
196
347
  // we return a map here so that any sorting for queries that exist
197
348
  // can be done in O(N) time
349
+ /**
350
+ * @deperecated use loadCustomEnts
351
+ */
198
352
  async function loadEntsFromClause(viewer, clause, options) {
199
353
  const rowOptions = {
200
354
  ...options,
@@ -202,65 +356,129 @@ async function loadEntsFromClause(viewer, clause, options) {
202
356
  context: viewer.context,
203
357
  };
204
358
  const rows = await loadRows(rowOptions);
205
- return await applyPrivacyPolicyForRows(viewer, rows, options);
359
+ return applyPrivacyPolicyForRowsDeprecated(viewer, rows, options);
206
360
  }
207
361
  exports.loadEntsFromClause = loadEntsFromClause;
208
362
  async function loadCustomEnts(viewer, options, query) {
209
363
  const rows = await loadCustomData(options, query, viewer.context);
210
- const result = new Array(rows.length);
211
- await Promise.all(rows.map(async (row, idx) => {
212
- const ent = new options.ent(viewer, row);
213
- let privacyEnt = await applyPrivacyPolicyForEnt(viewer, ent, row, options);
214
- if (privacyEnt) {
215
- result[idx] = privacyEnt;
216
- }
217
- }));
218
- // filter ents that aren't visible because of privacy
219
- return result.filter((r) => r !== undefined);
364
+ return applyPrivacyPolicyForRows(viewer, rows, options);
220
365
  }
221
366
  exports.loadCustomEnts = loadCustomEnts;
222
367
  function isClause(opts) {
223
368
  const cls = opts;
224
369
  return cls.clause !== undefined && cls.values !== undefined;
225
370
  }
226
- function isRawQuery(opts) {
371
+ function isParameterizedQuery(opts) {
227
372
  return opts.query !== undefined;
228
373
  }
374
+ /**
375
+ * Note that if there's default read transformations (e.g. soft delete) and a clause is passed in
376
+ * either as Clause or QueryDataOptions without {disableTransformations: true}, the default transformation
377
+ * (e.g. soft delete) is applied.
378
+ *
379
+ * Passing a full SQL string or Paramterized SQL string doesn't apply it and the given string is sent to the
380
+ * database as written.
381
+ *
382
+ * e.g.
383
+ * Foo.loadCustom(opts, 'SELECT * FROM foo') // doesn't change the query
384
+ * Foo.loadCustom(opts, { query: 'SELECT * FROM foo WHERE id = ?', values: [1]}) // doesn't change the query
385
+ * Foo.loadCustom(opts, query.Eq('time', Date.now())) // changes the query
386
+ * Foo.loadCustom(opts, {
387
+ * clause: query.LessEq('time', Date.now()),
388
+ * limit: 100,
389
+ * orderby: 'time',
390
+ * }) // changes the query
391
+ * Foo.loadCustom(opts, {
392
+ * clause: query.LessEq('time', Date.now()),
393
+ * limit: 100,
394
+ * orderby: 'time',
395
+ * disableTransformations: false
396
+ * }) // doesn't change the query
397
+ *
398
+ * For queries that pass in a clause, we batch them with an underlying dataloader so that multiple queries with the same clause
399
+ * or parallel queries with the same clause are batched together.
400
+ *
401
+ * If a raw or parameterized query is passed in, we don't attempt to batch them together and they're executed as is.
402
+ * If you end up with a scenario where you may need to coalesce or batch (non-clause) queries here, you should use some kind of memoization here.
403
+ */
229
404
  async function loadCustomData(options, query, context) {
405
+ const rows = await loadCustomDataImpl(options, query, context);
406
+ // prime the data so that subsequent fetches of the row with this id are a cache hit.
407
+ if (options.prime) {
408
+ const loader = options.loaderFactory.createLoader(context);
409
+ if (isPrimableLoader(loader) && loader.primeAll !== undefined) {
410
+ for (const row of rows) {
411
+ loader.primeAll(row);
412
+ }
413
+ }
414
+ }
415
+ return rows;
416
+ }
417
+ exports.loadCustomData = loadCustomData;
418
+ // NOTE: if you use a raw query or paramterized query with this,
419
+ // you should use `SELECT count(*) as count...`
420
+ async function loadCustomCount(options, query, context) {
421
+ // if clause, we'll use the loader and strong typing/coalescing it provides
422
+ if (typeof query !== "string" && isClause(query)) {
423
+ return options.loaderFactory.createCountLoader(context).load(query);
424
+ }
425
+ const rows = await loadCustomDataImpl({
426
+ ...options,
427
+ fields: ["count(1) as count"],
428
+ }, query, context);
429
+ if (rows.length) {
430
+ return parseInt(rows[0].count);
431
+ }
432
+ return 0;
433
+ }
434
+ exports.loadCustomCount = loadCustomCount;
435
+ function isPrimableLoader(loader) {
436
+ return loader != undefined;
437
+ }
438
+ async function loadCustomDataImpl(options, query, context) {
230
439
  if (typeof query === "string") {
231
440
  // no caching, perform raw query
232
- return await performRawQuery(query, [], []);
441
+ return performRawQuery(query, [], []);
233
442
  }
234
443
  else if (isClause(query)) {
235
- // this will have rudimentary caching but nothing crazy
236
- return await loadRows({
237
- ...options,
238
- clause: query,
239
- context: context,
240
- });
444
+ const r = await options.loaderFactory
445
+ .createTypedLoader(context)
446
+ .load(query);
447
+ return r;
241
448
  }
242
- else if (isRawQuery(query)) {
449
+ else if (isParameterizedQuery(query)) {
243
450
  // no caching, perform raw query
244
- return await performRawQuery(query.query, query.values || [], query.logValues);
451
+ return performRawQuery(query.query, query.values || [], query.logValues);
245
452
  }
246
453
  else {
247
454
  // this will have rudimentary caching but nothing crazy
248
- return await loadRows({
455
+ let cls = query.clause;
456
+ if (!query.disableTransformations) {
457
+ cls = clause.getCombinedClause(options.loaderFactory.options, query.clause);
458
+ }
459
+ return loadRows({
249
460
  ...query,
250
461
  ...options,
251
462
  context: context,
463
+ // @ts-expect-error
464
+ clause: cls,
252
465
  });
253
466
  }
254
467
  }
255
- exports.loadCustomData = loadCustomData;
256
468
  // Derived ents
469
+ // no ent caching
257
470
  async function loadDerivedEnt(viewer, data, loader) {
258
471
  const ent = new loader(viewer, data);
259
- return await applyPrivacyPolicyForEnt(viewer, ent, data, {
472
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, {
260
473
  ent: loader,
261
474
  });
475
+ if (r instanceof Error) {
476
+ return null;
477
+ }
478
+ return r;
262
479
  }
263
480
  exports.loadDerivedEnt = loadDerivedEnt;
481
+ // won't have caching yet either
264
482
  async function loadDerivedEntX(viewer, data, loader) {
265
483
  const ent = new loader(viewer, data);
266
484
  return await applyPrivacyPolicyForEntX(viewer, ent, data, { ent: loader });
@@ -269,19 +487,21 @@ exports.loadDerivedEntX = loadDerivedEntX;
269
487
  // everything calls into this two so should be fine
270
488
  // TODO is there a smarter way to not instantiate two objects here?
271
489
  async function applyPrivacyPolicyForEnt(viewer, ent, data, fieldPrivacyOptions) {
272
- if (ent) {
273
- const visible = await (0, privacy_1.applyPrivacyPolicy)(viewer, ent.getPrivacyPolicy(), ent);
274
- if (!visible) {
275
- return null;
276
- }
490
+ const error = await (0, privacy_1.applyPrivacyPolicyImpl)(viewer, ent.getPrivacyPolicy(), ent);
491
+ if (error === null) {
277
492
  return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
278
493
  }
279
- return null;
494
+ return error;
280
495
  }
281
496
  async function applyPrivacyPolicyForEntX(viewer, ent, data, options) {
282
- // this will throw
283
- await (0, privacy_1.applyPrivacyPolicyX)(viewer, ent.getPrivacyPolicy(), ent);
284
- return doFieldPrivacy(viewer, ent, data, options);
497
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, options);
498
+ if (r instanceof Error) {
499
+ throw r;
500
+ }
501
+ if (r === null) {
502
+ throw new Error(`couldn't apply privacyPoliy for ent ${ent.id}`);
503
+ }
504
+ return r;
285
505
  }
286
506
  async function doFieldPrivacy(viewer, ent, data, options) {
287
507
  if (!options.fieldPrivacy) {
@@ -289,16 +509,20 @@ async function doFieldPrivacy(viewer, ent, data, options) {
289
509
  }
290
510
  const promises = [];
291
511
  let somethingChanged = false;
512
+ const clone = { ...data };
513
+ const origData = {
514
+ ...data,
515
+ };
292
516
  for (const [k, policy] of options.fieldPrivacy) {
517
+ const curr = clone[k];
518
+ if (curr === null || curr === undefined) {
519
+ continue;
520
+ }
293
521
  promises.push((async () => {
294
522
  // don't do anything if key is null or for some reason missing
295
- const curr = data[k];
296
- if (curr === null || curr === undefined) {
297
- return;
298
- }
299
523
  const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
300
524
  if (!r) {
301
- data[k] = null;
525
+ clone[k] = null;
302
526
  somethingChanged = true;
303
527
  }
304
528
  })());
@@ -306,8 +530,11 @@ async function doFieldPrivacy(viewer, ent, data, options) {
306
530
  await Promise.all(promises);
307
531
  if (somethingChanged) {
308
532
  // have to create new instance
309
- return new options.ent(viewer, data);
533
+ const ent = new options.ent(viewer, clone);
534
+ ent.__setRawDBData(origData);
535
+ return ent;
310
536
  }
537
+ ent.__setRawDBData(origData);
311
538
  return ent;
312
539
  }
313
540
  function logQuery(query, logValues) {
@@ -317,6 +544,7 @@ function logQuery(query, logValues) {
317
544
  });
318
545
  (0, logger_1.logTrace)();
319
546
  }
547
+ exports.logQuery = logQuery;
320
548
  // TODO long term figure out if this API should be exposed
321
549
  async function loadRowX(options) {
322
550
  const result = await loadRow(options);
@@ -339,29 +567,26 @@ async function loadRow(options) {
339
567
  }
340
568
  const query = buildQuery(options);
341
569
  logQuery(query, options.clause.logValues());
342
- try {
343
- const pool = db_1.default.getInstance().getPool();
344
- const res = await pool.query(query, options.clause.values());
345
- if (res.rowCount != 1) {
346
- if (res.rowCount > 1) {
347
- (0, logger_1.log)("error", "got more than one row for query " + query);
348
- }
349
- return null;
350
- }
351
- // put the row in the cache...
352
- if (cache) {
353
- cache.primeCache(options, res.rows[0]);
570
+ const pool = db_1.default.getInstance().getPool();
571
+ const res = await pool.query(query, options.clause.values());
572
+ if (res.rowCount != 1) {
573
+ if (res.rowCount > 1) {
574
+ (0, logger_1.log)("error", "got more than one row for query " + query);
354
575
  }
355
- return res.rows[0];
356
- }
357
- catch (e) {
358
- // an example of an error being suppressed
359
- // another one. TODO https://github.com/lolopinto/ent/issues/862
360
- (0, logger_1.log)("error", e);
361
576
  return null;
362
577
  }
578
+ // put the row in the cache...
579
+ if (cache) {
580
+ cache.primeCache(options, res.rows[0]);
581
+ }
582
+ return res.rows[0];
363
583
  }
364
584
  exports.loadRow = loadRow;
585
+ var _logQueryWithError = false;
586
+ function ___setLogQueryErrorWithError(val) {
587
+ _logQueryWithError = val || false;
588
+ }
589
+ exports.___setLogQueryErrorWithError = ___setLogQueryErrorWithError;
365
590
  // this always goes to the db, no cache, nothing
366
591
  async function performRawQuery(query, values, logValues) {
367
592
  const pool = db_1.default.getInstance().getPool();
@@ -371,9 +596,11 @@ async function performRawQuery(query, values, logValues) {
371
596
  return res.rows;
372
597
  }
373
598
  catch (e) {
374
- // TODO need to change every query to catch an error!
375
- (0, logger_1.log)("error", e);
376
- return [];
599
+ if (_logQueryWithError) {
600
+ const msg = e.message;
601
+ throw new Error(`error \`${msg}\` running query: \`${query}\` with values: \`${logValues}\``);
602
+ }
603
+ throw e;
377
604
  }
378
605
  }
379
606
  exports.performRawQuery = performRawQuery;
@@ -400,17 +627,18 @@ function buildQuery(options) {
400
627
  const fields = options.fields.join(", ");
401
628
  // always start at 1
402
629
  const whereClause = options.clause.clause(1);
403
- let query = `SELECT ${fields} FROM ${options.tableName} WHERE ${whereClause}`;
630
+ const parts = [];
631
+ parts.push(`SELECT ${fields} FROM ${options.tableName} WHERE ${whereClause}`);
404
632
  if (options.groupby) {
405
- query = `${query} GROUP BY ${options.groupby}`;
633
+ parts.push(`GROUP BY ${options.groupby}`);
406
634
  }
407
635
  if (options.orderby) {
408
- query = `${query} ORDER BY ${options.orderby}`;
636
+ parts.push(`ORDER BY ${options.orderby}`);
409
637
  }
410
638
  if (options.limit) {
411
- query = `${query} LIMIT ${options.limit}`;
639
+ parts.push(`LIMIT ${options.limit}`);
412
640
  }
413
- return query;
641
+ return parts.join(" ");
414
642
  }
415
643
  exports.buildQuery = buildQuery;
416
644
  // this is used for queries when we select multiple ids at once
@@ -432,400 +660,32 @@ function buildGroupQuery(options) {
432
660
  ];
433
661
  }
434
662
  exports.buildGroupQuery = buildGroupQuery;
435
- class EditNodeOperation {
436
- constructor(options, existingEnt = null) {
437
- this.options = options;
438
- this.existingEnt = existingEnt;
439
- this.placeholderID = options.placeholderID;
440
- }
441
- resolve(executor) {
442
- if (!this.options.fieldsToResolve.length) {
443
- return;
444
- }
445
- let fields = this.options.fields;
446
- this.options.fieldsToResolve.forEach((fieldName) => {
447
- let value = fields[fieldName];
448
- if (!value) {
449
- throw new Error(`trying to resolve field ${fieldName} but not a valid field`);
450
- }
451
- let ent = executor.resolveValue(value.placeholderID);
452
- if (!ent) {
453
- throw new Error(`couldn't resolve field \`${fieldName}\` with value ${value.placeholderID}`);
454
- }
455
- fields[fieldName] = ent.id;
456
- });
457
- this.options.fields = fields;
458
- }
459
- hasData(data) {
460
- for (const _k in data) {
461
- return true;
462
- }
463
- return false;
464
- }
465
- async performWrite(queryer, context) {
466
- let options = {
467
- ...this.options,
468
- context,
469
- };
470
- if (this.existingEnt) {
471
- if (this.hasData(options.fields)) {
472
- // even this with returning * may not always work if transformed...
473
- // we can have a transformed flag to see if it should be returned?
474
- this.row = await editRow(queryer, options, this.existingEnt.id, "RETURNING *");
475
- }
476
- else {
477
- this.row = this.existingEnt["data"];
478
- }
479
- }
480
- else {
481
- this.row = await createRow(queryer, options, "RETURNING *");
482
- }
483
- }
484
- reloadRow(queryer, id, options) {
485
- // TODO this isn't always an ObjectLoader. should throw or figure out a way to get query
486
- // and run this on its own...
487
- const loader = this.options.loadEntOptions.loaderFactory.createLoader(options.context);
488
- const opts = loader.getOptions();
489
- let cls = clause.Eq(options.key, id);
490
- if (opts.clause) {
491
- let optionClause;
492
- if (typeof opts.clause === "function") {
493
- optionClause = opts.clause();
494
- }
495
- else {
496
- optionClause = opts.clause;
497
- }
498
- if (optionClause) {
499
- cls = clause.And(optionClause, cls);
500
- }
501
- }
502
- const query = buildQuery({
503
- fields: opts.fields.length ? opts.fields : ["*"],
504
- tableName: options.tableName,
505
- clause: cls,
506
- });
507
- // special case log here because we're not going through any of the normal
508
- // methods here because those are async and this is sync
509
- // this is the only place we're doing this so only handling here
510
- logQuery(query, [id]);
511
- const r = queryer.querySync(query, [id]);
512
- if (r.rows.length === 1) {
513
- this.row = r.rows[0];
514
- }
515
- }
516
- performWriteSync(queryer, context) {
517
- let options = {
518
- ...this.options,
519
- context,
520
- };
521
- if (this.existingEnt) {
522
- if (this.hasData(this.options.fields)) {
523
- editRowSync(queryer, options, this.existingEnt.id, "RETURNING *");
524
- this.reloadRow(queryer, this.existingEnt.id, options);
525
- }
526
- else {
527
- this.row = this.existingEnt["data"];
528
- }
529
- }
530
- else {
531
- createRowSync(queryer, options, "RETURNING *");
532
- const id = options.fields[options.key];
533
- this.reloadRow(queryer, id, options);
534
- }
535
- }
536
- returnedRow() {
537
- return this.row;
538
- }
539
- createdEnt(viewer) {
540
- if (!this.row) {
541
- return null;
542
- }
543
- return new this.options.loadEntOptions.ent(viewer, this.row);
544
- }
545
- }
546
- exports.EditNodeOperation = EditNodeOperation;
547
- class EdgeOperation {
548
- constructor(edgeInput, options) {
549
- this.edgeInput = edgeInput;
550
- this.options = options;
551
- }
552
- async preFetch(queryer, context) {
553
- let edgeData = await loadEdgeData(this.edgeInput.edgeType);
554
- if (!edgeData) {
555
- throw new Error(`error loading edge data for ${this.edgeInput.edgeType}`);
556
- }
557
- this.edgeData = edgeData;
558
- }
559
- async performWrite(queryer, context) {
560
- if (!this.edgeData) {
561
- throw new Error(`error fetching edgeData for type ${this.edgeInput.edgeType}`);
562
- }
563
- switch (this.options.operation) {
564
- case action_1.WriteOperation.Delete:
565
- return this.performDeleteWrite(queryer, this.edgeData, this.edgeInput, context);
566
- case action_1.WriteOperation.Insert:
567
- case action_1.WriteOperation.Edit:
568
- return this.performInsertWrite(queryer, this.edgeData, this.edgeInput, context);
569
- }
570
- }
571
- performWriteSync(queryer, context) {
572
- if (!this.edgeData) {
573
- throw new Error(`error fetching edgeData for type ${this.edgeInput.edgeType}`);
574
- }
575
- switch (this.options.operation) {
576
- case action_1.WriteOperation.Delete:
577
- return this.performDeleteWriteSync(queryer, this.edgeData, this.edgeInput, context);
578
- case action_1.WriteOperation.Insert:
579
- case action_1.WriteOperation.Edit:
580
- return this.performInsertWriteSync(queryer, this.edgeData, this.edgeInput, context);
581
- }
582
- }
583
- getDeleteRowParams(edgeData, edge, context) {
584
- return {
585
- options: {
586
- tableName: edgeData.edgeTable,
587
- context,
588
- },
589
- clause: clause.And(clause.Eq("id1", edge.id1), clause.Eq("id2", edge.id2), clause.Eq("edge_type", edge.edgeType)),
590
- };
591
- }
592
- async performDeleteWrite(q, edgeData, edge, context) {
593
- const params = this.getDeleteRowParams(edgeData, edge, context);
594
- return deleteRows(q, params.options, params.clause);
595
- }
596
- performDeleteWriteSync(q, edgeData, edge, context) {
597
- const params = this.getDeleteRowParams(edgeData, edge, context);
598
- return deleteRowsSync(q, params.options, params.clause);
599
- }
600
- getInsertRowParams(edgeData, edge, context) {
601
- const fields = {
602
- id1: edge.id1,
603
- id2: edge.id2,
604
- id1_type: edge.id1Type,
605
- id2_type: edge.id2Type,
606
- edge_type: edge.edgeType,
607
- data: edge.data || null,
608
- };
609
- if (edge.time) {
610
- fields["time"] = edge.time.toISOString();
611
- }
612
- else {
613
- // todo make this a schema field like what we do in generated base files...
614
- // maybe when actions exist?
615
- fields["time"] = new Date().toISOString();
616
- }
617
- return [
618
- {
619
- tableName: edgeData.edgeTable,
620
- fields: fields,
621
- fieldsToLog: fields,
622
- context,
623
- },
624
- "ON CONFLICT(id1, edge_type, id2) DO UPDATE SET data = EXCLUDED.data",
625
- ];
626
- }
627
- async performInsertWrite(q, edgeData, edge, context) {
628
- const [options, suffix] = this.getInsertRowParams(edgeData, edge, context);
629
- await createRow(q, options, suffix);
630
- }
631
- performInsertWriteSync(q, edgeData, edge, context) {
632
- const [options, suffix] = this.getInsertRowParams(edgeData, edge, context);
633
- createRowSync(q, options, suffix);
634
- }
635
- resolveImpl(executor, placeholder, desc) {
636
- let ent = executor.resolveValue(placeholder);
637
- if (!ent) {
638
- throw new Error(`could not resolve placeholder value ${placeholder} for ${desc} for edge ${this.edgeInput.edgeType}`);
639
- }
640
- if (ent.id === undefined) {
641
- throw new Error(`id of resolved ent is not defined`);
642
- }
643
- return [ent.id, ent.nodeType];
644
- }
645
- resolve(executor) {
646
- if (this.options.id1Placeholder) {
647
- [this.edgeInput.id1, this.edgeInput.id1Type] = this.resolveImpl(executor, this.edgeInput.id1, "id1");
648
- }
649
- if (this.options.id2Placeholder) {
650
- [this.edgeInput.id2, this.edgeInput.id2Type] = this.resolveImpl(executor, this.edgeInput.id2, "id2");
651
- }
652
- if (this.options.dataPlaceholder) {
653
- if (!this.edgeInput.data) {
654
- throw new Error(`data placeholder set but edgeInput data undefined`);
655
- }
656
- let [data, _] = this.resolveImpl(executor, this.edgeInput.data.toString(), "data");
657
- this.edgeInput.data = data.toString();
658
- }
659
- }
660
- symmetricEdge() {
661
- return new EdgeOperation({
662
- id1: this.edgeInput.id2,
663
- id1Type: this.edgeInput.id2Type,
664
- id2: this.edgeInput.id1,
665
- id2Type: this.edgeInput.id1Type,
666
- edgeType: this.edgeInput.edgeType,
667
- time: this.edgeInput.time,
668
- data: this.edgeInput.data,
669
- }, {
670
- operation: this.options.operation,
671
- id1Placeholder: this.options.id2Placeholder,
672
- id2Placeholder: this.options.id1Placeholder,
673
- dataPlaceholder: this.options.dataPlaceholder,
674
- });
675
- }
676
- inverseEdge(edgeData) {
677
- return new EdgeOperation({
678
- id1: this.edgeInput.id2,
679
- id1Type: this.edgeInput.id2Type,
680
- id2: this.edgeInput.id1,
681
- id2Type: this.edgeInput.id1Type,
682
- edgeType: edgeData.inverseEdgeType,
683
- time: this.edgeInput.time,
684
- data: this.edgeInput.data,
685
- }, {
686
- operation: this.options.operation,
687
- id1Placeholder: this.options.id2Placeholder,
688
- id2Placeholder: this.options.id1Placeholder,
689
- dataPlaceholder: this.options.dataPlaceholder,
690
- });
691
- }
692
- static resolveIDs(srcBuilder, // id1
693
- destID) {
694
- let destIDVal;
695
- let destPlaceholder = false;
696
- if (this.isBuilder(destID)) {
697
- destIDVal = destID.placeholderID;
698
- destPlaceholder = true;
699
- }
700
- else {
701
- destIDVal = destID;
702
- }
703
- let srcIDVal;
704
- let srcType;
705
- let srcPlaceholder = false;
706
- if (srcBuilder.existingEnt) {
707
- srcIDVal = srcBuilder.existingEnt.id;
708
- srcType = srcBuilder.existingEnt.nodeType;
709
- }
710
- else {
711
- srcPlaceholder = true;
712
- // get placeholder.
713
- srcIDVal = srcBuilder.placeholderID;
714
- // expected to be filled later
715
- srcType = "";
716
- }
717
- return [srcIDVal, srcType, srcPlaceholder, destIDVal, destPlaceholder];
718
- }
719
- static isBuilder(val) {
720
- return val.placeholderID !== undefined;
721
- }
722
- static resolveData(data) {
723
- if (!data) {
724
- return [undefined, false];
725
- }
726
- if (this.isBuilder(data)) {
727
- return [data.placeholderID.toString(), true];
728
- }
729
- return [data, false];
730
- }
731
- static inboundEdge(builder, edgeType, id1, nodeType, options) {
732
- let [id2Val, id2Type, id2Placeholder, id1Val, id1Placeholder] = EdgeOperation.resolveIDs(builder, id1);
733
- let [data, dataPlaceholder] = EdgeOperation.resolveData(options?.data);
734
- const edge = {
735
- id1: id1Val,
736
- edgeType: edgeType,
737
- id2: id2Val,
738
- id2Type: id2Type,
739
- id1Type: nodeType,
740
- ...options,
741
- };
742
- if (data) {
743
- edge.data = data;
744
- }
745
- return new EdgeOperation(edge, {
746
- operation: action_1.WriteOperation.Insert,
747
- id2Placeholder,
748
- id1Placeholder,
749
- dataPlaceholder,
750
- });
751
- }
752
- static outboundEdge(builder, edgeType, id2, nodeType, options) {
753
- let [id1Val, id1Type, id1Placeholder, id2Val, id2Placeholder] = EdgeOperation.resolveIDs(builder, id2);
754
- let [data, dataPlaceholder] = EdgeOperation.resolveData(options?.data);
755
- const edge = {
756
- id1: id1Val,
757
- edgeType: edgeType,
758
- id2: id2Val,
759
- id2Type: nodeType,
760
- id1Type: id1Type,
761
- ...options,
762
- };
763
- if (data) {
764
- edge.data = data;
765
- }
766
- return new EdgeOperation(edge, {
767
- operation: action_1.WriteOperation.Insert,
768
- id1Placeholder,
769
- id2Placeholder,
770
- dataPlaceholder,
771
- });
772
- }
773
- static removeInboundEdge(builder, edgeType, id1) {
774
- if (!builder.existingEnt) {
775
- throw new Error("cannot remove an edge from a non-existing ent");
776
- }
777
- const edge = {
778
- id1: id1,
779
- edgeType: edgeType,
780
- id2: builder.existingEnt.id,
781
- id2Type: "",
782
- id1Type: "",
783
- };
784
- return new EdgeOperation(edge, {
785
- operation: action_1.WriteOperation.Delete,
786
- });
787
- }
788
- static removeOutboundEdge(builder, edgeType, id2) {
789
- if (!builder.existingEnt) {
790
- throw new Error("cannot remove an edge from a non-existing ent");
791
- }
792
- const edge = {
793
- id2: id2,
794
- edgeType: edgeType,
795
- id1: builder.existingEnt.id,
796
- id2Type: "",
797
- id1Type: "",
798
- };
799
- return new EdgeOperation(edge, {
800
- operation: action_1.WriteOperation.Delete,
801
- });
802
- }
803
- }
804
- exports.EdgeOperation = EdgeOperation;
805
663
  function isSyncQueryer(queryer) {
806
664
  return queryer.execSync !== undefined;
807
665
  }
808
666
  async function mutateRow(queryer, query, values, logValues, options) {
809
667
  logQuery(query, logValues);
810
668
  let cache = options.context?.cache;
669
+ let res;
811
670
  try {
812
- let res;
813
671
  if (isSyncQueryer(queryer)) {
814
672
  res = queryer.execSync(query, values);
815
673
  }
816
674
  else {
817
675
  res = await queryer.exec(query, values);
818
676
  }
819
- if (cache) {
820
- cache.clearCache();
677
+ }
678
+ catch (e) {
679
+ if (_logQueryWithError) {
680
+ const msg = e.message;
681
+ throw new Error(`error \`${msg}\` running query: \`${query}\``);
821
682
  }
822
- return res;
683
+ throw e;
823
684
  }
824
- catch (err) {
825
- // TODO:::why is this not rethrowing?
826
- (0, logger_1.log)("error", err);
827
- throw err;
685
+ if (cache) {
686
+ cache.clearCache();
828
687
  }
688
+ return res;
829
689
  }
830
690
  function mutateRowSync(queryer, query, values, logValues, options) {
831
691
  logQuery(query, logValues);
@@ -837,10 +697,12 @@ function mutateRowSync(queryer, query, values, logValues, options) {
837
697
  }
838
698
  return res;
839
699
  }
840
- catch (err) {
841
- // TODO:::why is this not rethrowing?
842
- (0, logger_1.log)("error", err);
843
- throw err;
700
+ catch (e) {
701
+ if (_logQueryWithError) {
702
+ const msg = e.message;
703
+ throw new Error(`error \`${msg}\` running query: \`${query}\``);
704
+ }
705
+ throw e;
844
706
  }
845
707
  }
846
708
  function buildInsertQuery(options, suffix) {
@@ -867,8 +729,26 @@ function buildInsertQuery(options, suffix) {
867
729
  const cols = fields.join(", ");
868
730
  const vals = valsString.join(", ");
869
731
  let query = `INSERT INTO ${options.tableName} (${cols}) VALUES (${vals})`;
732
+ if (options.onConflict) {
733
+ let onConflict = "";
734
+ if (options.onConflict.onConflictConstraint) {
735
+ onConflict = `ON CONFLICT ON CONSTRAINT ${options.onConflict.onConflictConstraint}`;
736
+ }
737
+ else {
738
+ onConflict = `ON CONFLICT(${options.onConflict.onConflictCols.join(", ")})`;
739
+ }
740
+ if (options.onConflict.updateCols?.length) {
741
+ onConflict += ` DO UPDATE SET ${options.onConflict.updateCols
742
+ .map((f) => `${f} = EXCLUDED.${f}`)
743
+ .join(", ")}`;
744
+ }
745
+ else {
746
+ onConflict += ` DO NOTHING`;
747
+ }
748
+ query = query + " " + onConflict;
749
+ }
870
750
  if (suffix) {
871
- query = query + " " + suffix;
751
+ query += " " + suffix;
872
752
  }
873
753
  return [query, values, logValues];
874
754
  }
@@ -893,32 +773,47 @@ function createRowSync(queryer, options, suffix) {
893
773
  return null;
894
774
  }
895
775
  exports.createRowSync = createRowSync;
896
- function buildUpdateQuery(options, id, suffix) {
776
+ function buildUpdateQuery(options, suffix) {
897
777
  let valsString = [];
898
778
  let values = [];
899
779
  let logValues = [];
900
780
  const dialect = db_1.default.getDialect();
901
781
  let idx = 1;
902
782
  for (const key in options.fields) {
903
- values.push(options.fields[key]);
904
- if (options.fieldsToLog) {
905
- logValues.push(options.fieldsToLog[key]);
906
- }
907
- if (dialect === db_1.Dialect.Postgres) {
908
- valsString.push(`${key} = $${idx}`);
909
- idx++;
783
+ if (options.expressions && options.expressions.has(key)) {
784
+ const cls = options.expressions.get(key);
785
+ valsString.push(`${key} = ${cls.clause(idx)}`);
786
+ // TODO need to test a clause with more than one value...
787
+ const newVals = cls.values();
788
+ idx += newVals.length;
789
+ values.push(...newVals);
790
+ logValues.push(...cls.logValues());
910
791
  }
911
792
  else {
912
- valsString.push(`${key} = ?`);
793
+ const val = options.fields[key];
794
+ values.push(val);
795
+ if (options.fieldsToLog) {
796
+ logValues.push(options.fieldsToLog[key]);
797
+ }
798
+ // TODO would be nice to use clause here. need update version of the queries so that
799
+ // we don't have to handle dialect specifics here
800
+ // can't use clause because of IS NULL
801
+ // valsString.push(clause.Eq(key, val).clause(idx));
802
+ if (dialect === db_1.Dialect.Postgres) {
803
+ valsString.push(`${key} = $${idx}`);
804
+ }
805
+ else {
806
+ valsString.push(`${key} = ?`);
807
+ }
808
+ idx++;
913
809
  }
914
810
  }
915
811
  const vals = valsString.join(", ");
916
812
  let query = `UPDATE ${options.tableName} SET ${vals} WHERE `;
917
- if (dialect === db_1.Dialect.Postgres) {
918
- query = query + `${options.key} = $${idx}`;
919
- }
920
- else {
921
- query = query + `${options.key} = ?`;
813
+ query = query + options.whereClause.clause(idx);
814
+ values.push(...options.whereClause.values());
815
+ if (options.fieldsToLog) {
816
+ logValues.push(...options.whereClause.logValues());
922
817
  }
923
818
  if (suffix) {
924
819
  query = query + " " + suffix;
@@ -926,10 +821,8 @@ function buildUpdateQuery(options, id, suffix) {
926
821
  return [query, values, logValues];
927
822
  }
928
823
  exports.buildUpdateQuery = buildUpdateQuery;
929
- async function editRow(queryer, options, id, suffix) {
930
- const [query, values, logValues] = buildUpdateQuery(options, id, suffix);
931
- // add id as value to prepared query
932
- values.push(id);
824
+ async function editRow(queryer, options, suffix) {
825
+ const [query, values, logValues] = buildUpdateQuery(options, suffix);
933
826
  const res = await mutateRow(queryer, query, values, logValues, options);
934
827
  if (res?.rowCount == 1) {
935
828
  // for now assume id primary key
@@ -940,10 +833,8 @@ async function editRow(queryer, options, id, suffix) {
940
833
  return null;
941
834
  }
942
835
  exports.editRow = editRow;
943
- function editRowSync(queryer, options, id, suffix) {
944
- const [query, values, logValues] = buildUpdateQuery(options, id, suffix);
945
- // add id as value to prepared query
946
- values.push(id);
836
+ function editRowSync(queryer, options, suffix) {
837
+ const [query, values, logValues] = buildUpdateQuery(options, suffix);
947
838
  const res = mutateRowSync(queryer, query, values, logValues, options);
948
839
  if (res?.rowCount == 1) {
949
840
  // for now assume id primary key
@@ -964,27 +855,6 @@ function deleteRowsSync(queryer, options, cls) {
964
855
  mutateRowSync(queryer, query, cls.values(), cls.logValues(), options);
965
856
  }
966
857
  exports.deleteRowsSync = deleteRowsSync;
967
- class DeleteNodeOperation {
968
- constructor(id, options) {
969
- this.id = id;
970
- this.options = options;
971
- }
972
- async performWrite(queryer, context) {
973
- let options = {
974
- ...this.options,
975
- context,
976
- };
977
- return deleteRows(queryer, options, clause.Eq("id", this.id));
978
- }
979
- performWriteSync(queryer, context) {
980
- let options = {
981
- ...this.options,
982
- context,
983
- };
984
- return deleteRowsSync(queryer, options, clause.Eq("id", this.id));
985
- }
986
- }
987
- exports.DeleteNodeOperation = DeleteNodeOperation;
988
858
  class AssocEdge {
989
859
  constructor(data) {
990
860
  this.id1 = data.id1;
@@ -994,21 +864,22 @@ class AssocEdge {
994
864
  this.edgeType = data.edge_type;
995
865
  this.time = data.time;
996
866
  this.data = data.data;
867
+ this.rawData = data;
868
+ }
869
+ __getRawData() {
870
+ // incase there's extra db fields. useful for tests
871
+ // in production, a subclass of this should be in use so we won't need this...
872
+ return this.rawData;
997
873
  }
998
874
  getCursor() {
999
875
  return getCursor({
1000
876
  row: this,
1001
- col: "time",
1002
- conv: (t) => {
1003
- if (typeof t === "string") {
1004
- return Date.parse(t);
1005
- }
1006
- return t.getTime();
1007
- },
877
+ col: "id2",
1008
878
  });
1009
879
  }
1010
880
  }
1011
881
  exports.AssocEdge = AssocEdge;
882
+ // TODO eventually update this for sortCol time unique keys
1012
883
  function getCursor(opts) {
1013
884
  const { row, col, conv } = opts;
1014
885
  // row: Data, col: string, conv?: (any) => any) {
@@ -1086,54 +957,89 @@ const edgeFields = [
1086
957
  "time",
1087
958
  "data",
1088
959
  ];
1089
- exports.DefaultLimit = 1000;
1090
- // TODO default limit from somewhere
1091
- function defaultEdgeQueryOptions(id1, edgeType) {
960
+ let defaultLimit = 1000;
961
+ function setDefaultLimit(limit) {
962
+ defaultLimit = limit;
963
+ }
964
+ exports.setDefaultLimit = setDefaultLimit;
965
+ function getDefaultLimit() {
966
+ return defaultLimit;
967
+ }
968
+ exports.getDefaultLimit = getDefaultLimit;
969
+ function defaultEdgeQueryOptions(id1, edgeType, id2) {
970
+ let cls = clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType));
971
+ if (id2) {
972
+ cls = clause.And(cls, clause.Eq("id2", id2));
973
+ }
1092
974
  return {
1093
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
975
+ clause: cls,
1094
976
  orderby: "time DESC",
1095
- limit: exports.DefaultLimit,
977
+ limit: defaultLimit,
1096
978
  };
1097
979
  }
1098
- exports.defaultEdgeQueryOptions = defaultEdgeQueryOptions;
1099
980
  async function loadEdges(options) {
1100
981
  return loadCustomEdges({ ...options, ctr: AssocEdge });
1101
982
  }
1102
983
  exports.loadEdges = loadEdges;
984
+ function getEdgeClauseAndFields(cls, options) {
985
+ let fields = edgeFields;
986
+ const transformEdgeRead = (0, global_schema_1.__getGlobalSchema)()?.transformEdgeRead;
987
+ if (transformEdgeRead) {
988
+ const transformClause = transformEdgeRead();
989
+ if (!options.disableTransformations) {
990
+ cls = clause.And(cls, transformClause);
991
+ }
992
+ fields = edgeFields.concat(transformClause.columns());
993
+ }
994
+ return {
995
+ cls,
996
+ fields,
997
+ };
998
+ }
999
+ exports.getEdgeClauseAndFields = getEdgeClauseAndFields;
1103
1000
  async function loadCustomEdges(options) {
1104
- const { id1, edgeType, context } = options;
1001
+ const { cls: actualClause, fields, defaultOptions, tableName, } = await loadEgesInfo(options);
1002
+ const rows = await loadRows({
1003
+ tableName,
1004
+ fields: fields,
1005
+ clause: actualClause,
1006
+ orderby: options.queryOptions?.orderby || defaultOptions.orderby,
1007
+ limit: options.queryOptions?.limit || defaultOptions.limit,
1008
+ context: options.context,
1009
+ });
1010
+ return rows.map((row) => {
1011
+ return new options.ctr(row);
1012
+ });
1013
+ }
1014
+ exports.loadCustomEdges = loadCustomEdges;
1015
+ async function loadEgesInfo(options, id2) {
1016
+ const { id1, edgeType } = options;
1105
1017
  const edgeData = await loadEdgeData(edgeType);
1106
1018
  if (!edgeData) {
1107
1019
  throw new Error(`error loading edge data for ${edgeType}`);
1108
1020
  }
1109
- const defaultOptions = defaultEdgeQueryOptions(id1, edgeType);
1021
+ const defaultOptions = defaultEdgeQueryOptions(id1, edgeType, id2);
1110
1022
  let cls = defaultOptions.clause;
1111
1023
  if (options.queryOptions?.clause) {
1112
1024
  cls = clause.And(cls, options.queryOptions.clause);
1113
1025
  }
1114
- const rows = await loadRows({
1026
+ return {
1027
+ ...getEdgeClauseAndFields(cls, options),
1028
+ defaultOptions,
1115
1029
  tableName: edgeData.edgeTable,
1116
- fields: edgeFields,
1117
- clause: cls,
1118
- orderby: options.queryOptions?.orderby || defaultOptions.orderby,
1119
- limit: options.queryOptions?.limit || defaultOptions.limit,
1120
- context,
1121
- });
1122
- return rows.map((row) => {
1123
- return new options.ctr(row);
1124
- });
1030
+ };
1125
1031
  }
1126
- exports.loadCustomEdges = loadCustomEdges;
1127
1032
  async function loadUniqueEdge(options) {
1128
1033
  const { id1, edgeType, context } = options;
1129
1034
  const edgeData = await loadEdgeData(edgeType);
1130
1035
  if (!edgeData) {
1131
1036
  throw new Error(`error loading edge data for ${edgeType}`);
1132
1037
  }
1038
+ const { cls, fields } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
1133
1039
  const row = await loadRow({
1134
1040
  tableName: edgeData.edgeTable,
1135
- fields: edgeFields,
1136
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1041
+ fields: fields,
1042
+ clause: cls,
1137
1043
  context,
1138
1044
  });
1139
1045
  if (!row) {
@@ -1160,21 +1066,28 @@ async function loadRawEdgeCountX(options) {
1160
1066
  if (!edgeData) {
1161
1067
  throw new Error(`error loading edge data for ${edgeType}`);
1162
1068
  }
1069
+ const { cls } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
1163
1070
  const row = await loadRowX({
1164
1071
  tableName: edgeData.edgeTable,
1165
1072
  // sqlite needs as count otherwise it returns count(1)
1166
1073
  fields: ["count(1) as count"],
1167
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1074
+ clause: cls,
1168
1075
  context,
1169
1076
  });
1170
1077
  return parseInt(row["count"], 10) || 0;
1171
1078
  }
1172
1079
  exports.loadRawEdgeCountX = loadRawEdgeCountX;
1173
1080
  async function loadEdgeForID2(options) {
1174
- // TODO at some point, same as in go, we can be smart about this and have heuristics to determine if we fetch everything here or not
1175
- // we're assuming a cache here but not always true and this can be expensive if not...
1176
- const edges = await loadCustomEdges(options);
1177
- return edges.find((edge) => edge.id2 == options.id2);
1081
+ const { cls: actualClause, fields, tableName, } = await loadEgesInfo(options, options.id2);
1082
+ const row = await loadRow({
1083
+ tableName,
1084
+ fields: fields,
1085
+ clause: actualClause,
1086
+ context: options.context,
1087
+ });
1088
+ if (row) {
1089
+ return new options.ctr(row);
1090
+ }
1178
1091
  }
1179
1092
  exports.loadEdgeForID2 = loadEdgeForID2;
1180
1093
  async function loadNodesByEdge(viewer, id1, edgeType, options) {
@@ -1190,19 +1103,20 @@ async function loadNodesByEdge(viewer, id1, edgeType, options) {
1190
1103
  }
1191
1104
  exports.loadNodesByEdge = loadNodesByEdge;
1192
1105
  async function applyPrivacyPolicyForRow(viewer, options, row) {
1193
- if (!row) {
1194
- return null;
1195
- }
1196
- const ent = new options.ent(viewer, row);
1197
- return await applyPrivacyPolicyForEnt(viewer, ent, row, options);
1106
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
1107
+ return r instanceof Error ? null : r;
1198
1108
  }
1199
1109
  exports.applyPrivacyPolicyForRow = applyPrivacyPolicyForRow;
1110
+ async function applyPrivacyPolicyForRowImpl(viewer, options, row) {
1111
+ const ent = new options.ent(viewer, row);
1112
+ return applyPrivacyPolicyForEnt(viewer, ent, row, options);
1113
+ }
1200
1114
  async function applyPrivacyPolicyForRowX(viewer, options, row) {
1201
1115
  const ent = new options.ent(viewer, row);
1202
1116
  return await applyPrivacyPolicyForEntX(viewer, ent, row, options);
1203
1117
  }
1204
- exports.applyPrivacyPolicyForRowX = applyPrivacyPolicyForRowX;
1205
- async function applyPrivacyPolicyForRows(viewer, rows, options) {
1118
+ // deprecated. doesn't use entcache
1119
+ async function applyPrivacyPolicyForRowsDeprecated(viewer, rows, options) {
1206
1120
  let m = new Map();
1207
1121
  // apply privacy logic
1208
1122
  await Promise.all(rows.map(async (row) => {
@@ -1213,27 +1127,62 @@ async function applyPrivacyPolicyForRows(viewer, rows, options) {
1213
1127
  }));
1214
1128
  return m;
1215
1129
  }
1216
- exports.applyPrivacyPolicyForRows = applyPrivacyPolicyForRows;
1217
- async function loadEdgeWithConst(viewer, id1, id2, edgeEnum, edgeType) {
1218
- const edge = await loadEdgeForID2({
1219
- id1: id1,
1220
- id2: id2,
1221
- edgeType: edgeType,
1222
- context: viewer.context,
1223
- ctr: AssocEdge,
1224
- });
1225
- return [edgeEnum, edge];
1130
+ async function applyPrivacyPolicyForRows(viewer, rows, options) {
1131
+ const result = new Array(rows.length);
1132
+ if (!rows.length) {
1133
+ return [];
1134
+ }
1135
+ const entLoader = getEntLoader(viewer, options);
1136
+ await Promise.all(rows.map(async (row, idx) => {
1137
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options, entLoader);
1138
+ if (r instanceof ErrorWrapper) {
1139
+ return;
1140
+ }
1141
+ result[idx] = r;
1142
+ }));
1143
+ // filter ents that aren't visible because of privacy
1144
+ return result.filter((r) => r !== undefined);
1226
1145
  }
1146
+ exports.applyPrivacyPolicyForRows = applyPrivacyPolicyForRows;
1227
1147
  // given a viewer, an id pair, and a map of edgeEnum to EdgeType
1228
1148
  // return the edgeEnum that's set in the group
1229
1149
  async function getEdgeTypeInGroup(viewer, id1, id2, m) {
1230
1150
  let promises = [];
1231
- for (const [k, v] of m) {
1232
- promises.push(loadEdgeWithConst(viewer, id1, id2, k, v));
1151
+ const edgeDatas = await loadEdgeDatas(...Array.from(m.values()));
1152
+ let tableToEdgeEnumMap = new Map();
1153
+ for (const [edgeEnum, edgeType] of m) {
1154
+ const edgeData = edgeDatas.get(edgeType);
1155
+ if (!edgeData) {
1156
+ throw new Error(`could not load edge data for '${edgeType}'`);
1157
+ }
1158
+ const l = tableToEdgeEnumMap.get(edgeData.edgeTable) ?? [];
1159
+ l.push(edgeEnum);
1160
+ tableToEdgeEnumMap.set(edgeData.edgeTable, l);
1233
1161
  }
1162
+ tableToEdgeEnumMap.forEach((edgeEnums, tableName) => {
1163
+ promises.push((async () => {
1164
+ const edgeTypes = edgeEnums.map((edgeEnum) => m.get(edgeEnum));
1165
+ const { cls, fields } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.In("edge_type", edgeTypes), clause.Eq("id2", id2)), {});
1166
+ const rows = await loadRows({
1167
+ tableName,
1168
+ fields,
1169
+ clause: cls,
1170
+ context: viewer.context,
1171
+ });
1172
+ const row = rows[0];
1173
+ if (row) {
1174
+ const edgeType = row.edge_type;
1175
+ for (const [k, v] of m) {
1176
+ if (v === edgeType) {
1177
+ return [k, new AssocEdge(row)];
1178
+ }
1179
+ }
1180
+ }
1181
+ })());
1182
+ });
1234
1183
  const results = await Promise.all(promises);
1235
1184
  for (const res of results) {
1236
- if (res[1]) {
1185
+ if (res && res[1]) {
1237
1186
  return [res[0], res[1]];
1238
1187
  }
1239
1188
  }