@snowtop/ent 0.1.0-alpha13 → 0.1.0-alpha130

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 (173) hide show
  1. package/action/action.d.ts +33 -29
  2. package/action/action.js +22 -7
  3. package/action/executor.d.ts +3 -3
  4. package/action/executor.js +8 -3
  5. package/action/experimental_action.d.ts +32 -22
  6. package/action/experimental_action.js +35 -9
  7. package/action/index.d.ts +2 -0
  8. package/action/index.js +7 -1
  9. package/action/orchestrator.d.ts +32 -15
  10. package/action/orchestrator.js +249 -53
  11. package/action/privacy.d.ts +2 -2
  12. package/action/relative_value.d.ts +47 -0
  13. package/action/relative_value.js +125 -0
  14. package/action/transaction.d.ts +10 -0
  15. package/action/transaction.js +23 -0
  16. package/auth/auth.d.ts +1 -1
  17. package/core/base.d.ts +61 -37
  18. package/core/base.js +7 -1
  19. package/core/clause.d.ts +85 -40
  20. package/core/clause.js +375 -64
  21. package/core/config.d.ts +12 -1
  22. package/core/config.js +7 -1
  23. package/core/const.d.ts +3 -0
  24. package/core/const.js +6 -0
  25. package/core/context.d.ts +6 -4
  26. package/core/context.js +20 -2
  27. package/core/convert.d.ts +1 -1
  28. package/core/date.js +1 -5
  29. package/core/db.d.ts +11 -8
  30. package/core/db.js +20 -8
  31. package/core/ent.d.ts +86 -30
  32. package/core/ent.js +626 -197
  33. package/core/global_schema.d.ts +7 -0
  34. package/core/global_schema.js +51 -0
  35. package/core/loaders/assoc_count_loader.d.ts +3 -2
  36. package/core/loaders/assoc_count_loader.js +10 -2
  37. package/core/loaders/assoc_edge_loader.d.ts +2 -2
  38. package/core/loaders/assoc_edge_loader.js +8 -11
  39. package/core/loaders/index.d.ts +1 -1
  40. package/core/loaders/index.js +1 -3
  41. package/core/loaders/index_loader.d.ts +3 -3
  42. package/core/loaders/loader.d.ts +2 -2
  43. package/core/loaders/loader.js +5 -5
  44. package/core/loaders/object_loader.d.ts +32 -11
  45. package/core/loaders/object_loader.js +225 -78
  46. package/core/loaders/query_loader.d.ts +7 -13
  47. package/core/loaders/query_loader.js +52 -11
  48. package/core/loaders/raw_count_loader.d.ts +2 -2
  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 +25 -24
  53. package/core/privacy.js +21 -25
  54. package/core/query/assoc_query.d.ts +7 -6
  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 +84 -0
  58. package/core/query/custom_query.d.ts +20 -5
  59. package/core/query/custom_query.js +87 -12
  60. package/core/query/index.d.ts +1 -0
  61. package/core/query/index.js +3 -1
  62. package/core/query/query.d.ts +8 -4
  63. package/core/query/query.js +101 -53
  64. package/core/query/shared_assoc_test.d.ts +2 -1
  65. package/core/query/shared_assoc_test.js +35 -45
  66. package/core/query/shared_test.d.ts +8 -1
  67. package/core/query/shared_test.js +470 -236
  68. package/core/viewer.d.ts +3 -3
  69. package/core/viewer.js +1 -1
  70. package/graphql/graphql.d.ts +51 -19
  71. package/graphql/graphql.js +160 -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/edge_connection.d.ts +9 -9
  78. package/graphql/query/page_info.d.ts +1 -1
  79. package/graphql/query/shared_assoc_test.js +1 -1
  80. package/graphql/query/shared_edge_connection.js +1 -19
  81. package/graphql/scalars/orderby_direction.d.ts +2 -0
  82. package/graphql/scalars/orderby_direction.js +15 -0
  83. package/imports/dataz/example1/_auth.js +128 -47
  84. package/imports/dataz/example1/_viewer.js +87 -39
  85. package/imports/index.d.ts +6 -1
  86. package/imports/index.js +19 -4
  87. package/index.d.ts +13 -5
  88. package/index.js +21 -7
  89. package/package.json +17 -17
  90. package/parse_schema/parse.d.ts +31 -9
  91. package/parse_schema/parse.js +155 -13
  92. package/schema/base_schema.d.ts +5 -3
  93. package/schema/base_schema.js +6 -0
  94. package/schema/field.d.ts +78 -21
  95. package/schema/field.js +231 -71
  96. package/schema/index.d.ts +2 -2
  97. package/schema/index.js +5 -1
  98. package/schema/json_field.d.ts +16 -4
  99. package/schema/json_field.js +32 -2
  100. package/schema/schema.d.ts +89 -19
  101. package/schema/schema.js +11 -13
  102. package/schema/struct_field.d.ts +15 -3
  103. package/schema/struct_field.js +117 -22
  104. package/schema/union_field.d.ts +1 -1
  105. package/scripts/custom_compiler.js +10 -6
  106. package/scripts/custom_graphql.js +128 -31
  107. package/scripts/migrate_v0.1.js +36 -0
  108. package/scripts/move_types.js +120 -0
  109. package/scripts/read_schema.js +20 -5
  110. package/testutils/action/complex_schemas.d.ts +69 -0
  111. package/testutils/action/complex_schemas.js +398 -0
  112. package/testutils/builder.d.ts +41 -47
  113. package/testutils/builder.js +76 -49
  114. package/testutils/db/fixture.d.ts +10 -0
  115. package/testutils/db/fixture.js +26 -0
  116. package/testutils/db/{test_db.d.ts → temp_db.d.ts} +24 -8
  117. package/testutils/db/{test_db.js → temp_db.js} +182 -45
  118. package/testutils/db/value.d.ts +7 -0
  119. package/testutils/db/value.js +251 -0
  120. package/testutils/db_mock.d.ts +16 -4
  121. package/testutils/db_mock.js +52 -7
  122. package/testutils/db_time_zone.d.ts +4 -0
  123. package/testutils/db_time_zone.js +41 -0
  124. package/testutils/ent-graphql-tests/index.d.ts +7 -1
  125. package/testutils/ent-graphql-tests/index.js +52 -23
  126. package/testutils/fake_comms.js +1 -1
  127. package/testutils/fake_data/const.d.ts +2 -1
  128. package/testutils/fake_data/const.js +3 -0
  129. package/testutils/fake_data/fake_contact.d.ts +7 -3
  130. package/testutils/fake_data/fake_contact.js +13 -7
  131. package/testutils/fake_data/fake_event.d.ts +4 -1
  132. package/testutils/fake_data/fake_event.js +7 -6
  133. package/testutils/fake_data/fake_tag.d.ts +36 -0
  134. package/testutils/fake_data/fake_tag.js +89 -0
  135. package/testutils/fake_data/fake_user.d.ts +8 -5
  136. package/testutils/fake_data/fake_user.js +16 -15
  137. package/testutils/fake_data/index.js +5 -1
  138. package/testutils/fake_data/internal.d.ts +2 -0
  139. package/testutils/fake_data/internal.js +7 -1
  140. package/testutils/fake_data/tag_query.d.ts +13 -0
  141. package/testutils/fake_data/tag_query.js +43 -0
  142. package/testutils/fake_data/test_helpers.d.ts +11 -4
  143. package/testutils/fake_data/test_helpers.js +28 -12
  144. package/testutils/fake_data/user_query.d.ts +13 -6
  145. package/testutils/fake_data/user_query.js +54 -22
  146. package/testutils/fake_log.d.ts +3 -3
  147. package/testutils/fake_log.js +1 -1
  148. package/testutils/parse_sql.d.ts +6 -0
  149. package/testutils/parse_sql.js +16 -2
  150. package/testutils/test_edge_global_schema.d.ts +15 -0
  151. package/testutils/test_edge_global_schema.js +62 -0
  152. package/testutils/write.d.ts +2 -2
  153. package/testutils/write.js +33 -7
  154. package/tsc/ast.d.ts +25 -2
  155. package/tsc/ast.js +141 -17
  156. package/tsc/compilerOptions.js +5 -1
  157. package/tsc/move_generated.d.ts +1 -0
  158. package/tsc/move_generated.js +164 -0
  159. package/tsc/transform.d.ts +22 -0
  160. package/tsc/transform.js +181 -0
  161. package/tsc/transform_action.d.ts +22 -0
  162. package/tsc/transform_action.js +183 -0
  163. package/tsc/transform_ent.d.ts +17 -0
  164. package/tsc/transform_ent.js +60 -0
  165. package/tsc/transform_schema.d.ts +27 -0
  166. package/{scripts → tsc}/transform_schema.js +146 -117
  167. package/graphql/enums.d.ts +0 -3
  168. package/graphql/enums.js +0 -25
  169. package/scripts/move_generated.js +0 -142
  170. package/scripts/transform_code.js +0 -113
  171. package/scripts/transform_schema.d.ts +0 -1
  172. /package/scripts/{move_generated.d.ts → migrate_v0.1.d.ts} +0 -0
  173. /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,16 @@ 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.loadNodesByEdge = exports.loadEdgeForID2 = exports.loadRawEdgeCountX = exports.loadUniqueNode = exports.loadUniqueEdge = exports.loadCustomEdges = exports.getEdgeClauseAndFields = exports.loadEdges = 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.RawQueryOperation = 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;
30
+ exports.getEdgeTypeInGroup = exports.applyPrivacyPolicyForRows = exports.applyPrivacyPolicyForRow = void 0;
26
31
  const db_1 = __importStar(require("./db"));
27
32
  const privacy_1 = require("./privacy");
28
33
  const clause = __importStar(require("./clause"));
29
34
  const action_1 = require("../action");
30
35
  const logger_1 = require("./logger");
31
36
  const dataloader_1 = __importDefault(require("dataloader"));
37
+ const schema_1 = require("../schema/schema");
38
+ const global_schema_1 = require("./global_schema");
32
39
  // TODO kill this and createDataLoader
33
40
  class cacheMap {
34
41
  constructor(options) {
@@ -38,7 +45,7 @@ class cacheMap {
38
45
  get(key) {
39
46
  const ret = this.m.get(key);
40
47
  if (ret) {
41
- (0, logger_1.log)("query", {
48
+ (0, logger_1.log)("cache", {
42
49
  "dataloader-cache-hit": key,
43
50
  "tableName": this.options.tableName,
44
51
  });
@@ -55,12 +62,41 @@ class cacheMap {
55
62
  return this.m.clear();
56
63
  }
57
64
  }
65
+ class entCacheMap {
66
+ constructor(viewer, options) {
67
+ this.viewer = viewer;
68
+ this.options = options;
69
+ this.m = new Map();
70
+ this.logEnabled = false;
71
+ this.logEnabled = (0, logger_1.logEnabled)("cache");
72
+ }
73
+ get(id) {
74
+ const ret = this.m.get(id);
75
+ if (this.logEnabled && ret) {
76
+ const key = getEntKey(this.viewer, id, this.options);
77
+ (0, logger_1.log)("cache", {
78
+ "ent-cache-hit": key,
79
+ });
80
+ }
81
+ return ret;
82
+ }
83
+ set(key, value) {
84
+ return this.m.set(key, value);
85
+ }
86
+ delete(key) {
87
+ return this.m.delete(key);
88
+ }
89
+ clear() {
90
+ return this.m.clear();
91
+ }
92
+ }
58
93
  function createDataLoader(options) {
59
94
  const loaderOptions = {};
60
95
  // if query logging is enabled, we should log what's happening with loader
61
96
  if ((0, logger_1.logEnabled)("query")) {
62
97
  loaderOptions.cacheMap = new cacheMap(options);
63
98
  }
99
+ // something here brokwn with strict:true
64
100
  return new dataloader_1.default(async (ids) => {
65
101
  if (!ids.length) {
66
102
  return [];
@@ -84,28 +120,151 @@ function createDataLoader(options) {
84
120
  return result;
85
121
  }, loaderOptions);
86
122
  }
87
- // Ent accessors
123
+ // used to wrap errors that would eventually be thrown in ents
124
+ // not an Error because DataLoader automatically rejects that
125
+ class ErrorWrapper {
126
+ constructor(error) {
127
+ this.error = error;
128
+ }
129
+ }
130
+ function createEntLoader(viewer, options, map) {
131
+ // share the cache across loaders even if we create a new instance
132
+ const loaderOptions = {};
133
+ loaderOptions.cacheMap = map;
134
+ return new dataloader_1.default(async (ids) => {
135
+ if (!ids.length) {
136
+ return [];
137
+ }
138
+ let result = [];
139
+ const tableName = options.loaderFactory.options?.tableName;
140
+ const loader = options.loaderFactory.createLoader(viewer.context);
141
+ const rows = await loader.loadMany(ids);
142
+ // this is a loader which should return the same order based on passed-in ids
143
+ // so let's depend on that...
144
+ for (let idx = 0; idx < rows.length; idx++) {
145
+ const row = rows[idx];
146
+ // db error
147
+ if (row instanceof Error) {
148
+ result[idx] = row;
149
+ continue;
150
+ }
151
+ else if (!row) {
152
+ if (tableName) {
153
+ result[idx] = new ErrorWrapper(new Error(`couldn't find row for value ${ids[idx]} in table ${tableName}`));
154
+ }
155
+ else {
156
+ result[idx] = new ErrorWrapper(new Error(`couldn't find row for value ${ids[idx]}`));
157
+ }
158
+ }
159
+ else {
160
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
161
+ if (r instanceof Error) {
162
+ result[idx] = new ErrorWrapper(r);
163
+ }
164
+ else {
165
+ result[idx] = r;
166
+ }
167
+ }
168
+ }
169
+ return result;
170
+ }, loaderOptions);
171
+ }
172
+ class EntLoader {
173
+ constructor(viewer, options) {
174
+ this.viewer = viewer;
175
+ this.options = options;
176
+ this.map = new entCacheMap(viewer, options);
177
+ this.loader = createEntLoader(this.viewer, this.options, this.map);
178
+ }
179
+ getMap() {
180
+ return this.map;
181
+ }
182
+ async load(id) {
183
+ return this.loader.load(id);
184
+ }
185
+ async loadMany(ids) {
186
+ return this.loader.loadMany(ids);
187
+ }
188
+ prime(id, ent) {
189
+ this.loader.prime(id, ent);
190
+ }
191
+ clear(id) {
192
+ this.loader.clear(id);
193
+ }
194
+ clearAll() {
195
+ this.loader.clearAll();
196
+ }
197
+ }
198
+ function getEntLoader(viewer, options) {
199
+ if (!viewer.context?.cache) {
200
+ return new EntLoader(viewer, options);
201
+ }
202
+ const name = `ent-loader:${viewer.instanceKey()}:${options.loaderFactory.name}`;
203
+ return viewer.context.cache.getLoaderWithLoadMany(name, () => new EntLoader(viewer, options));
204
+ }
205
+ function getEntKey(viewer, id, options) {
206
+ return `${viewer.instanceKey()}:${options.loaderFactory.name}:${id}`;
207
+ }
208
+ exports.getEntKey = getEntKey;
88
209
  async function loadEnt(viewer, id, options) {
89
- const row = await options.loaderFactory.createLoader(viewer.context).load(id);
90
- return await applyPrivacyPolicyForRow(viewer, options, row);
210
+ if (typeof id !== "string" &&
211
+ typeof id !== "number" &&
212
+ typeof id !== "bigint") {
213
+ throw new Error(`invalid id ${id} passed to loadEnt`);
214
+ }
215
+ const r = await getEntLoader(viewer, options).load(id);
216
+ return r instanceof ErrorWrapper ? null : r;
91
217
  }
92
218
  exports.loadEnt = loadEnt;
219
+ async function applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options,
220
+ // can pass in loader when calling this for multi-id cases...
221
+ loader) {
222
+ if (!loader) {
223
+ loader = getEntLoader(viewer, options);
224
+ }
225
+ // TODO every row.id needs to be audited...
226
+ // https://github.com/lolopinto/ent/issues/1064
227
+ const id = row.id;
228
+ // we should check the ent loader cache to see if this is already there
229
+ // 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
230
+ const result = loader.getMap().get(id);
231
+ if (result !== undefined) {
232
+ return result;
233
+ }
234
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
235
+ if (r instanceof Error) {
236
+ loader.prime(id, new ErrorWrapper(r));
237
+ return new ErrorWrapper(r);
238
+ }
239
+ else {
240
+ loader.prime(id, r);
241
+ return r;
242
+ }
243
+ }
93
244
  // this is the same implementation-wise (right now) as loadEnt. it's just clearer that it's not loaded via ID.
94
245
  // used for load via email address etc
95
246
  async function loadEntViaKey(viewer, key, options) {
96
247
  const row = await options.loaderFactory
97
248
  .createLoader(viewer.context)
98
249
  .load(key);
99
- return await applyPrivacyPolicyForRow(viewer, options, row);
250
+ if (!row) {
251
+ return null;
252
+ }
253
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options);
254
+ return r instanceof ErrorWrapper ? null : r;
100
255
  }
101
256
  exports.loadEntViaKey = loadEntViaKey;
102
257
  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}`);
258
+ if (typeof id !== "string" &&
259
+ typeof id !== "number" &&
260
+ typeof id !== "bigint") {
261
+ throw new Error(`invalid id ${id} passed to loadEntX`);
107
262
  }
108
- return await applyPrivacyPolicyForRowX(viewer, options, row);
263
+ const r = await getEntLoader(viewer, options).load(id);
264
+ if (r instanceof ErrorWrapper) {
265
+ throw r.error;
266
+ }
267
+ return r;
109
268
  }
110
269
  exports.loadEntX = loadEntX;
111
270
  async function loadEntXViaKey(viewer, key, options) {
@@ -116,9 +275,16 @@ async function loadEntXViaKey(viewer, key, options) {
116
275
  // todo make this better
117
276
  throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${key}`);
118
277
  }
119
- return await applyPrivacyPolicyForRowX(viewer, options, row);
278
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options);
279
+ if (r instanceof ErrorWrapper) {
280
+ throw r.error;
281
+ }
282
+ return r;
120
283
  }
121
284
  exports.loadEntXViaKey = loadEntXViaKey;
285
+ /**
286
+ * @deprecated use loadCustomEnts
287
+ */
122
288
  async function loadEntFromClause(viewer, options, clause) {
123
289
  const rowOptions = {
124
290
  ...options,
@@ -126,12 +292,18 @@ async function loadEntFromClause(viewer, options, clause) {
126
292
  context: viewer.context,
127
293
  };
128
294
  const row = await loadRow(rowOptions);
129
- return await applyPrivacyPolicyForRow(viewer, options, row);
295
+ if (row === null) {
296
+ return null;
297
+ }
298
+ return applyPrivacyPolicyForRow(viewer, options, row);
130
299
  }
131
300
  exports.loadEntFromClause = loadEntFromClause;
132
301
  // same as loadEntFromClause
133
302
  // only works for ents where primary key is "id"
134
303
  // use loadEnt with a loaderFactory if different
304
+ /**
305
+ * @deprecated use loadCustomEnts
306
+ */
135
307
  async function loadEntXFromClause(viewer, options, clause) {
136
308
  const rowOptions = {
137
309
  ...options,
@@ -146,37 +318,19 @@ async function loadEnts(viewer, options, ...ids) {
146
318
  if (!ids.length) {
147
319
  return new Map();
148
320
  }
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
321
+ // result
158
322
  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);
323
+ const ret = await getEntLoader(viewer, options).loadMany(ids);
324
+ for (const r of ret) {
325
+ if (r instanceof Error) {
326
+ throw r;
169
327
  }
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);
328
+ if (r instanceof ErrorWrapper) {
329
+ continue;
330
+ }
331
+ m.set(r.id, r);
176
332
  }
177
333
  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
334
  }
181
335
  exports.loadEnts = loadEnts;
182
336
  // calls loadEnts and returns the results sorted in the order they were passed in
@@ -195,6 +349,9 @@ async function loadEntsList(viewer, options, ...ids) {
195
349
  exports.loadEntsList = loadEntsList;
196
350
  // we return a map here so that any sorting for queries that exist
197
351
  // can be done in O(N) time
352
+ /**
353
+ * @deperecated use loadCustomEnts
354
+ */
198
355
  async function loadEntsFromClause(viewer, clause, options) {
199
356
  const rowOptions = {
200
357
  ...options,
@@ -202,65 +359,129 @@ async function loadEntsFromClause(viewer, clause, options) {
202
359
  context: viewer.context,
203
360
  };
204
361
  const rows = await loadRows(rowOptions);
205
- return await applyPrivacyPolicyForRows(viewer, rows, options);
362
+ return applyPrivacyPolicyForRowsDeprecated(viewer, rows, options);
206
363
  }
207
364
  exports.loadEntsFromClause = loadEntsFromClause;
208
365
  async function loadCustomEnts(viewer, options, query) {
209
366
  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);
367
+ return applyPrivacyPolicyForRows(viewer, rows, options);
220
368
  }
221
369
  exports.loadCustomEnts = loadCustomEnts;
222
370
  function isClause(opts) {
223
371
  const cls = opts;
224
372
  return cls.clause !== undefined && cls.values !== undefined;
225
373
  }
226
- function isRawQuery(opts) {
374
+ function isParameterizedQuery(opts) {
227
375
  return opts.query !== undefined;
228
376
  }
377
+ /**
378
+ * Note that if there's default read transformations (e.g. soft delete) and a clause is passed in
379
+ * either as Clause or QueryDataOptions without {disableTransformations: true}, the default transformation
380
+ * (e.g. soft delete) is applied.
381
+ *
382
+ * Passing a full SQL string or Paramterized SQL string doesn't apply it and the given string is sent to the
383
+ * database as written.
384
+ *
385
+ * e.g.
386
+ * Foo.loadCustom(opts, 'SELECT * FROM foo') // doesn't change the query
387
+ * Foo.loadCustom(opts, { query: 'SELECT * FROM foo WHERE id = ?', values: [1]}) // doesn't change the query
388
+ * Foo.loadCustom(opts, query.Eq('time', Date.now())) // changes the query
389
+ * Foo.loadCustom(opts, {
390
+ * clause: query.LessEq('time', Date.now()),
391
+ * limit: 100,
392
+ * orderby: 'time',
393
+ * }) // changes the query
394
+ * Foo.loadCustom(opts, {
395
+ * clause: query.LessEq('time', Date.now()),
396
+ * limit: 100,
397
+ * orderby: 'time',
398
+ * disableTransformations: false
399
+ * }) // doesn't change the query
400
+ *
401
+ * For queries that pass in a clause, we batch them with an underlying dataloader so that multiple queries with the same clause
402
+ * or parallel queries with the same clause are batched together.
403
+ *
404
+ * If a raw or parameterized query is passed in, we don't attempt to batch them together and they're executed as is.
405
+ * 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.
406
+ */
229
407
  async function loadCustomData(options, query, context) {
408
+ const rows = await loadCustomDataImpl(options, query, context);
409
+ // prime the data so that subsequent fetches of the row with this id are a cache hit.
410
+ if (options.prime) {
411
+ const loader = options.loaderFactory.createLoader(context);
412
+ if (isPrimableLoader(loader) && loader.primeAll !== undefined) {
413
+ for (const row of rows) {
414
+ loader.primeAll(row);
415
+ }
416
+ }
417
+ }
418
+ return rows;
419
+ }
420
+ exports.loadCustomData = loadCustomData;
421
+ // NOTE: if you use a raw query or paramterized query with this,
422
+ // you should use `SELECT count(*) as count...`
423
+ async function loadCustomCount(options, query, context) {
424
+ // if clause, we'll use the loader and strong typing/coalescing it provides
425
+ if (typeof query !== "string" && isClause(query)) {
426
+ return options.loaderFactory.createCountLoader(context).load(query);
427
+ }
428
+ const rows = await loadCustomDataImpl({
429
+ ...options,
430
+ fields: ["count(1) as count"],
431
+ }, query, context);
432
+ if (rows.length) {
433
+ return parseInt(rows[0].count);
434
+ }
435
+ return 0;
436
+ }
437
+ exports.loadCustomCount = loadCustomCount;
438
+ function isPrimableLoader(loader) {
439
+ return loader != undefined;
440
+ }
441
+ async function loadCustomDataImpl(options, query, context) {
230
442
  if (typeof query === "string") {
231
443
  // no caching, perform raw query
232
- return await performRawQuery(query, [], []);
444
+ return performRawQuery(query, [], []);
233
445
  }
234
446
  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
- });
447
+ const r = await options.loaderFactory
448
+ .createTypedLoader(context)
449
+ .load(query);
450
+ return r;
241
451
  }
242
- else if (isRawQuery(query)) {
452
+ else if (isParameterizedQuery(query)) {
243
453
  // no caching, perform raw query
244
- return await performRawQuery(query.query, query.values || [], query.logValues);
454
+ return performRawQuery(query.query, query.values || [], query.logValues);
245
455
  }
246
456
  else {
247
457
  // this will have rudimentary caching but nothing crazy
248
- return await loadRows({
458
+ let cls = query.clause;
459
+ if (!query.disableTransformations) {
460
+ cls = clause.getCombinedClause(options.loaderFactory.options, query.clause);
461
+ }
462
+ return loadRows({
249
463
  ...query,
250
464
  ...options,
251
465
  context: context,
466
+ // @ts-expect-error
467
+ clause: cls,
252
468
  });
253
469
  }
254
470
  }
255
- exports.loadCustomData = loadCustomData;
256
471
  // Derived ents
472
+ // no ent caching
257
473
  async function loadDerivedEnt(viewer, data, loader) {
258
474
  const ent = new loader(viewer, data);
259
- return await applyPrivacyPolicyForEnt(viewer, ent, data, {
475
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, {
260
476
  ent: loader,
261
477
  });
478
+ if (r instanceof Error) {
479
+ return null;
480
+ }
481
+ return r;
262
482
  }
263
483
  exports.loadDerivedEnt = loadDerivedEnt;
484
+ // won't have caching yet either
264
485
  async function loadDerivedEntX(viewer, data, loader) {
265
486
  const ent = new loader(viewer, data);
266
487
  return await applyPrivacyPolicyForEntX(viewer, ent, data, { ent: loader });
@@ -269,19 +490,21 @@ exports.loadDerivedEntX = loadDerivedEntX;
269
490
  // everything calls into this two so should be fine
270
491
  // TODO is there a smarter way to not instantiate two objects here?
271
492
  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
- }
493
+ const error = await (0, privacy_1.applyPrivacyPolicyImpl)(viewer, ent.getPrivacyPolicy(), ent);
494
+ if (error === null) {
277
495
  return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
278
496
  }
279
- return null;
497
+ return error;
280
498
  }
281
499
  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);
500
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, options);
501
+ if (r instanceof Error) {
502
+ throw r;
503
+ }
504
+ if (r === null) {
505
+ throw new Error(`couldn't apply privacyPoliy for ent ${ent.id}`);
506
+ }
507
+ return r;
285
508
  }
286
509
  async function doFieldPrivacy(viewer, ent, data, options) {
287
510
  if (!options.fieldPrivacy) {
@@ -289,13 +512,16 @@ async function doFieldPrivacy(viewer, ent, data, options) {
289
512
  }
290
513
  const promises = [];
291
514
  let somethingChanged = false;
515
+ const origData = {
516
+ ...data,
517
+ };
292
518
  for (const [k, policy] of options.fieldPrivacy) {
519
+ const curr = data[k];
520
+ if (curr === null || curr === undefined) {
521
+ continue;
522
+ }
293
523
  promises.push((async () => {
294
524
  // 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
525
  const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
300
526
  if (!r) {
301
527
  data[k] = null;
@@ -306,8 +532,11 @@ async function doFieldPrivacy(viewer, ent, data, options) {
306
532
  await Promise.all(promises);
307
533
  if (somethingChanged) {
308
534
  // have to create new instance
309
- return new options.ent(viewer, data);
535
+ const ent = new options.ent(viewer, data);
536
+ ent.__setRawDBData(origData);
537
+ return ent;
310
538
  }
539
+ ent.__setRawDBData(origData);
311
540
  return ent;
312
541
  }
313
542
  function logQuery(query, logValues) {
@@ -317,6 +546,7 @@ function logQuery(query, logValues) {
317
546
  });
318
547
  (0, logger_1.logTrace)();
319
548
  }
549
+ exports.logQuery = logQuery;
320
550
  // TODO long term figure out if this API should be exposed
321
551
  async function loadRowX(options) {
322
552
  const result = await loadRow(options);
@@ -339,29 +569,26 @@ async function loadRow(options) {
339
569
  }
340
570
  const query = buildQuery(options);
341
571
  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]);
572
+ const pool = db_1.default.getInstance().getPool();
573
+ const res = await pool.query(query, options.clause.values());
574
+ if (res.rowCount != 1) {
575
+ if (res.rowCount > 1) {
576
+ (0, logger_1.log)("error", "got more than one row for query " + query);
354
577
  }
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
578
  return null;
362
579
  }
580
+ // put the row in the cache...
581
+ if (cache) {
582
+ cache.primeCache(options, res.rows[0]);
583
+ }
584
+ return res.rows[0];
363
585
  }
364
586
  exports.loadRow = loadRow;
587
+ var _logQueryWithError = false;
588
+ function ___setLogQueryErrorWithError(val) {
589
+ _logQueryWithError = val || false;
590
+ }
591
+ exports.___setLogQueryErrorWithError = ___setLogQueryErrorWithError;
365
592
  // this always goes to the db, no cache, nothing
366
593
  async function performRawQuery(query, values, logValues) {
367
594
  const pool = db_1.default.getInstance().getPool();
@@ -371,9 +598,11 @@ async function performRawQuery(query, values, logValues) {
371
598
  return res.rows;
372
599
  }
373
600
  catch (e) {
374
- // TODO need to change every query to catch an error!
375
- (0, logger_1.log)("error", e);
376
- return [];
601
+ if (_logQueryWithError) {
602
+ const msg = e.message;
603
+ throw new Error(`error \`${msg}\` running query: \`${query}\` with values: \`${logValues}\``);
604
+ }
605
+ throw e;
377
606
  }
378
607
  }
379
608
  exports.performRawQuery = performRawQuery;
@@ -432,10 +661,41 @@ function buildGroupQuery(options) {
432
661
  ];
433
662
  }
434
663
  exports.buildGroupQuery = buildGroupQuery;
664
+ class RawQueryOperation {
665
+ constructor(queries) {
666
+ this.queries = queries;
667
+ }
668
+ async performWrite(queryer, context) {
669
+ for (const q of this.queries) {
670
+ if (typeof q === "string") {
671
+ logQuery(q, []);
672
+ await queryer.query(q);
673
+ }
674
+ else {
675
+ logQuery(q.query, q.logValues || []);
676
+ await queryer.query(q.query, q.values);
677
+ }
678
+ }
679
+ }
680
+ performWriteSync(queryer, context) {
681
+ for (const q of this.queries) {
682
+ if (typeof q === "string") {
683
+ logQuery(q, []);
684
+ queryer.execSync(q);
685
+ }
686
+ else {
687
+ logQuery(q.query, q.logValues || []);
688
+ queryer.execSync(q.query, q.values);
689
+ }
690
+ }
691
+ }
692
+ }
693
+ exports.RawQueryOperation = RawQueryOperation;
435
694
  class EditNodeOperation {
436
695
  constructor(options, existingEnt = null) {
437
696
  this.options = options;
438
697
  this.existingEnt = existingEnt;
698
+ this.row = null;
439
699
  this.placeholderID = options.placeholderID;
440
700
  }
441
701
  resolve(executor) {
@@ -471,9 +731,10 @@ class EditNodeOperation {
471
731
  if (this.hasData(options.fields)) {
472
732
  // even this with returning * may not always work if transformed...
473
733
  // we can have a transformed flag to see if it should be returned?
474
- this.row = await editRow(queryer, options, this.existingEnt.id, "RETURNING *");
734
+ this.row = await editRow(queryer, options, "RETURNING *");
475
735
  }
476
736
  else {
737
+ // @ts-ignore
477
738
  this.row = this.existingEnt["data"];
478
739
  }
479
740
  }
@@ -496,7 +757,8 @@ class EditNodeOperation {
496
757
  optionClause = opts.clause;
497
758
  }
498
759
  if (optionClause) {
499
- cls = clause.And(optionClause, cls);
760
+ // @ts-expect-error ID|string mismatch
761
+ cls = clause.And(cls, optionClause);
500
762
  }
501
763
  }
502
764
  const query = buildQuery({
@@ -520,10 +782,11 @@ class EditNodeOperation {
520
782
  };
521
783
  if (this.existingEnt) {
522
784
  if (this.hasData(this.options.fields)) {
523
- editRowSync(queryer, options, this.existingEnt.id, "RETURNING *");
785
+ editRowSync(queryer, options, "RETURNING *");
524
786
  this.reloadRow(queryer, this.existingEnt.id, options);
525
787
  }
526
788
  else {
789
+ // @ts-ignore
527
790
  this.row = this.existingEnt["data"];
528
791
  }
529
792
  }
@@ -545,7 +808,8 @@ class EditNodeOperation {
545
808
  }
546
809
  exports.EditNodeOperation = EditNodeOperation;
547
810
  class EdgeOperation {
548
- constructor(edgeInput, options) {
811
+ constructor(builder, edgeInput, options) {
812
+ this.builder = builder;
549
813
  this.edgeInput = edgeInput;
550
814
  this.options = options;
551
815
  }
@@ -581,7 +845,32 @@ class EdgeOperation {
581
845
  }
582
846
  }
583
847
  getDeleteRowParams(edgeData, edge, context) {
848
+ let transformed = null;
849
+ let op = schema_1.SQLStatementOperation.Delete;
850
+ let updateData = null;
851
+ // TODO respect disableTransformations
852
+ const transformedEdgeWrite = (0, global_schema_1.__getGlobalSchema)()?.transformEdgeWrite;
853
+ if (transformedEdgeWrite) {
854
+ transformed = transformedEdgeWrite({
855
+ op: schema_1.SQLStatementOperation.Delete,
856
+ edge,
857
+ });
858
+ if (transformed) {
859
+ op = transformed.op;
860
+ if (transformed.op === schema_1.SQLStatementOperation.Insert) {
861
+ throw new Error(`cannot currently transform a delete into an insert`);
862
+ }
863
+ if (transformed.op === schema_1.SQLStatementOperation.Update) {
864
+ if (!transformed.data) {
865
+ throw new Error(`cannot transform a delete into an update without providing data`);
866
+ }
867
+ updateData = transformed.data;
868
+ }
869
+ }
870
+ }
584
871
  return {
872
+ op,
873
+ updateData,
585
874
  options: {
586
875
  tableName: edgeData.edgeTable,
587
876
  context,
@@ -591,11 +880,36 @@ class EdgeOperation {
591
880
  }
592
881
  async performDeleteWrite(q, edgeData, edge, context) {
593
882
  const params = this.getDeleteRowParams(edgeData, edge, context);
594
- return deleteRows(q, params.options, params.clause);
883
+ if (params.op === schema_1.SQLStatementOperation.Delete) {
884
+ return deleteRows(q, params.options, params.clause);
885
+ }
886
+ else {
887
+ if (params.op !== schema_1.SQLStatementOperation.Update) {
888
+ throw new Error(`invalid operation ${params.op}`);
889
+ }
890
+ await editRow(q, {
891
+ tableName: params.options.tableName,
892
+ whereClause: params.clause,
893
+ fields: params.updateData,
894
+ fieldsToLog: params.updateData,
895
+ });
896
+ }
595
897
  }
596
898
  performDeleteWriteSync(q, edgeData, edge, context) {
597
899
  const params = this.getDeleteRowParams(edgeData, edge, context);
598
- return deleteRowsSync(q, params.options, params.clause);
900
+ if (params.op === schema_1.SQLStatementOperation.Delete) {
901
+ return deleteRowsSync(q, params.options, params.clause);
902
+ }
903
+ else {
904
+ if (params.op !== schema_1.SQLStatementOperation.Update) {
905
+ throw new Error(`invalid operation ${params.op}`);
906
+ }
907
+ editRowSync(q, {
908
+ tableName: params.options.tableName,
909
+ whereClause: params.clause,
910
+ fields: params.updateData,
911
+ });
912
+ }
599
913
  }
600
914
  getInsertRowParams(edgeData, edge, context) {
601
915
  const fields = {
@@ -614,6 +928,32 @@ class EdgeOperation {
614
928
  // maybe when actions exist?
615
929
  fields["time"] = new Date().toISOString();
616
930
  }
931
+ const onConflictFields = ["data"];
932
+ const extraEdgeFields = (0, global_schema_1.__getGlobalSchema)()?.extraEdgeFields;
933
+ if (extraEdgeFields) {
934
+ for (const name in extraEdgeFields) {
935
+ const f = extraEdgeFields[name];
936
+ if (f.defaultValueOnCreate) {
937
+ const storageKey = (0, schema_1.getStorageKey)(f, name);
938
+ fields[storageKey] = f.defaultValueOnCreate(this.builder, {});
939
+ // onconflict make sure we override the default values
940
+ // e.g. setting deleted_at = null for soft delete
941
+ onConflictFields.push(storageKey);
942
+ }
943
+ }
944
+ }
945
+ // TODO respect disableTransformations
946
+ let transformed = null;
947
+ const transformEdgeWrite = (0, global_schema_1.__getGlobalSchema)()?.transformEdgeWrite;
948
+ if (transformEdgeWrite) {
949
+ transformed = transformEdgeWrite({
950
+ op: schema_1.SQLStatementOperation.Insert,
951
+ edge,
952
+ });
953
+ if (transformed) {
954
+ throw new Error(`transforming an insert edge not currently supported`);
955
+ }
956
+ }
617
957
  return [
618
958
  {
619
959
  tableName: edgeData.edgeTable,
@@ -621,7 +961,9 @@ class EdgeOperation {
621
961
  fieldsToLog: fields,
622
962
  context,
623
963
  },
624
- "ON CONFLICT(id1, edge_type, id2) DO UPDATE SET data = EXCLUDED.data",
964
+ `ON CONFLICT(id1, edge_type, id2) DO UPDATE SET ${onConflictFields
965
+ .map((f) => `${f} = EXCLUDED.${f}`)
966
+ .join(", ")}`,
625
967
  ];
626
968
  }
627
969
  async performInsertWrite(q, edgeData, edge, context) {
@@ -658,7 +1000,7 @@ class EdgeOperation {
658
1000
  }
659
1001
  }
660
1002
  symmetricEdge() {
661
- return new EdgeOperation({
1003
+ return new EdgeOperation(this.builder, {
662
1004
  id1: this.edgeInput.id2,
663
1005
  id1Type: this.edgeInput.id2Type,
664
1006
  id2: this.edgeInput.id1,
@@ -674,7 +1016,7 @@ class EdgeOperation {
674
1016
  });
675
1017
  }
676
1018
  inverseEdge(edgeData) {
677
- return new EdgeOperation({
1019
+ return new EdgeOperation(this.builder, {
678
1020
  id1: this.edgeInput.id2,
679
1021
  id1Type: this.edgeInput.id2Type,
680
1022
  id2: this.edgeInput.id1,
@@ -742,7 +1084,7 @@ class EdgeOperation {
742
1084
  if (data) {
743
1085
  edge.data = data;
744
1086
  }
745
- return new EdgeOperation(edge, {
1087
+ return new EdgeOperation(builder, edge, {
746
1088
  operation: action_1.WriteOperation.Insert,
747
1089
  id2Placeholder,
748
1090
  id1Placeholder,
@@ -763,7 +1105,7 @@ class EdgeOperation {
763
1105
  if (data) {
764
1106
  edge.data = data;
765
1107
  }
766
- return new EdgeOperation(edge, {
1108
+ return new EdgeOperation(builder, edge, {
767
1109
  operation: action_1.WriteOperation.Insert,
768
1110
  id1Placeholder,
769
1111
  id2Placeholder,
@@ -781,7 +1123,7 @@ class EdgeOperation {
781
1123
  id2Type: "",
782
1124
  id1Type: "",
783
1125
  };
784
- return new EdgeOperation(edge, {
1126
+ return new EdgeOperation(builder, edge, {
785
1127
  operation: action_1.WriteOperation.Delete,
786
1128
  });
787
1129
  }
@@ -796,7 +1138,7 @@ class EdgeOperation {
796
1138
  id2Type: "",
797
1139
  id1Type: "",
798
1140
  };
799
- return new EdgeOperation(edge, {
1141
+ return new EdgeOperation(builder, edge, {
800
1142
  operation: action_1.WriteOperation.Delete,
801
1143
  });
802
1144
  }
@@ -808,24 +1150,26 @@ function isSyncQueryer(queryer) {
808
1150
  async function mutateRow(queryer, query, values, logValues, options) {
809
1151
  logQuery(query, logValues);
810
1152
  let cache = options.context?.cache;
1153
+ let res;
811
1154
  try {
812
- let res;
813
1155
  if (isSyncQueryer(queryer)) {
814
1156
  res = queryer.execSync(query, values);
815
1157
  }
816
1158
  else {
817
1159
  res = await queryer.exec(query, values);
818
1160
  }
819
- if (cache) {
820
- cache.clearCache();
1161
+ }
1162
+ catch (e) {
1163
+ if (_logQueryWithError) {
1164
+ const msg = e.message;
1165
+ throw new Error(`error \`${msg}\` running query: \`${query}\``);
821
1166
  }
822
- return res;
1167
+ throw e;
823
1168
  }
824
- catch (err) {
825
- // TODO:::why is this not rethrowing?
826
- (0, logger_1.log)("error", err);
827
- throw err;
1169
+ if (cache) {
1170
+ cache.clearCache();
828
1171
  }
1172
+ return res;
829
1173
  }
830
1174
  function mutateRowSync(queryer, query, values, logValues, options) {
831
1175
  logQuery(query, logValues);
@@ -837,10 +1181,12 @@ function mutateRowSync(queryer, query, values, logValues, options) {
837
1181
  }
838
1182
  return res;
839
1183
  }
840
- catch (err) {
841
- // TODO:::why is this not rethrowing?
842
- (0, logger_1.log)("error", err);
843
- throw err;
1184
+ catch (e) {
1185
+ if (_logQueryWithError) {
1186
+ const msg = e.message;
1187
+ throw new Error(`error \`${msg}\` running query: \`${query}\``);
1188
+ }
1189
+ throw e;
844
1190
  }
845
1191
  }
846
1192
  function buildInsertQuery(options, suffix) {
@@ -893,32 +1239,47 @@ function createRowSync(queryer, options, suffix) {
893
1239
  return null;
894
1240
  }
895
1241
  exports.createRowSync = createRowSync;
896
- function buildUpdateQuery(options, id, suffix) {
1242
+ function buildUpdateQuery(options, suffix) {
897
1243
  let valsString = [];
898
1244
  let values = [];
899
1245
  let logValues = [];
900
1246
  const dialect = db_1.default.getDialect();
901
1247
  let idx = 1;
902
1248
  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++;
1249
+ if (options.expressions && options.expressions.has(key)) {
1250
+ const cls = options.expressions.get(key);
1251
+ valsString.push(`${key} = ${cls.clause(idx)}`);
1252
+ // TODO need to test a clause with more than one value...
1253
+ const newVals = cls.values();
1254
+ idx += newVals.length;
1255
+ values.push(...newVals);
1256
+ logValues.push(...cls.logValues());
910
1257
  }
911
1258
  else {
912
- valsString.push(`${key} = ?`);
1259
+ const val = options.fields[key];
1260
+ values.push(val);
1261
+ if (options.fieldsToLog) {
1262
+ logValues.push(options.fieldsToLog[key]);
1263
+ }
1264
+ // TODO would be nice to use clause here. need update version of the queries so that
1265
+ // we don't have to handle dialect specifics here
1266
+ // can't use clause because of IS NULL
1267
+ // valsString.push(clause.Eq(key, val).clause(idx));
1268
+ if (dialect === db_1.Dialect.Postgres) {
1269
+ valsString.push(`${key} = $${idx}`);
1270
+ }
1271
+ else {
1272
+ valsString.push(`${key} = ?`);
1273
+ }
1274
+ idx++;
913
1275
  }
914
1276
  }
915
1277
  const vals = valsString.join(", ");
916
1278
  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} = ?`;
1279
+ query = query + options.whereClause.clause(idx);
1280
+ values.push(...options.whereClause.values());
1281
+ if (options.fieldsToLog) {
1282
+ logValues.push(...options.whereClause.logValues());
922
1283
  }
923
1284
  if (suffix) {
924
1285
  query = query + " " + suffix;
@@ -926,10 +1287,8 @@ function buildUpdateQuery(options, id, suffix) {
926
1287
  return [query, values, logValues];
927
1288
  }
928
1289
  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);
1290
+ async function editRow(queryer, options, suffix) {
1291
+ const [query, values, logValues] = buildUpdateQuery(options, suffix);
933
1292
  const res = await mutateRow(queryer, query, values, logValues, options);
934
1293
  if (res?.rowCount == 1) {
935
1294
  // for now assume id primary key
@@ -940,10 +1299,8 @@ async function editRow(queryer, options, id, suffix) {
940
1299
  return null;
941
1300
  }
942
1301
  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);
1302
+ function editRowSync(queryer, options, suffix) {
1303
+ const [query, values, logValues] = buildUpdateQuery(options, suffix);
947
1304
  const res = mutateRowSync(queryer, query, values, logValues, options);
948
1305
  if (res?.rowCount == 1) {
949
1306
  // for now assume id primary key
@@ -994,21 +1351,22 @@ class AssocEdge {
994
1351
  this.edgeType = data.edge_type;
995
1352
  this.time = data.time;
996
1353
  this.data = data.data;
1354
+ this.rawData = data;
1355
+ }
1356
+ __getRawData() {
1357
+ // incase there's extra db fields. useful for tests
1358
+ // in production, a subclass of this should be in use so we won't need this...
1359
+ return this.rawData;
997
1360
  }
998
1361
  getCursor() {
999
1362
  return getCursor({
1000
1363
  row: this,
1001
- col: "time",
1002
- conv: (t) => {
1003
- if (typeof t === "string") {
1004
- return Date.parse(t);
1005
- }
1006
- return t.getTime();
1007
- },
1364
+ col: "id2",
1008
1365
  });
1009
1366
  }
1010
1367
  }
1011
1368
  exports.AssocEdge = AssocEdge;
1369
+ // TODO eventually update this for sortCol time unique keys
1012
1370
  function getCursor(opts) {
1013
1371
  const { row, col, conv } = opts;
1014
1372
  // row: Data, col: string, conv?: (any) => any) {
@@ -1088,52 +1446,80 @@ const edgeFields = [
1088
1446
  ];
1089
1447
  exports.DefaultLimit = 1000;
1090
1448
  // TODO default limit from somewhere
1091
- function defaultEdgeQueryOptions(id1, edgeType) {
1449
+ function defaultEdgeQueryOptions(id1, edgeType, id2) {
1450
+ let cls = clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType));
1451
+ if (id2) {
1452
+ cls = clause.And(cls, clause.Eq("id2", id2));
1453
+ }
1092
1454
  return {
1093
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1455
+ clause: cls,
1094
1456
  orderby: "time DESC",
1095
1457
  limit: exports.DefaultLimit,
1096
1458
  };
1097
1459
  }
1098
- exports.defaultEdgeQueryOptions = defaultEdgeQueryOptions;
1099
1460
  async function loadEdges(options) {
1100
1461
  return loadCustomEdges({ ...options, ctr: AssocEdge });
1101
1462
  }
1102
1463
  exports.loadEdges = loadEdges;
1464
+ function getEdgeClauseAndFields(cls, options) {
1465
+ let fields = edgeFields;
1466
+ const transformEdgeRead = (0, global_schema_1.__getGlobalSchema)()?.transformEdgeRead;
1467
+ if (transformEdgeRead) {
1468
+ const transformClause = transformEdgeRead();
1469
+ if (!options.disableTransformations) {
1470
+ cls = clause.And(cls, transformClause);
1471
+ }
1472
+ fields = edgeFields.concat(transformClause.columns());
1473
+ }
1474
+ return {
1475
+ cls,
1476
+ fields,
1477
+ };
1478
+ }
1479
+ exports.getEdgeClauseAndFields = getEdgeClauseAndFields;
1103
1480
  async function loadCustomEdges(options) {
1104
- const { id1, edgeType, context } = options;
1481
+ const { cls: actualClause, fields, defaultOptions, tableName, } = await loadEgesInfo(options);
1482
+ const rows = await loadRows({
1483
+ tableName,
1484
+ fields: fields,
1485
+ clause: actualClause,
1486
+ orderby: options.queryOptions?.orderby || defaultOptions.orderby,
1487
+ limit: options.queryOptions?.limit || defaultOptions.limit,
1488
+ context: options.context,
1489
+ });
1490
+ return rows.map((row) => {
1491
+ return new options.ctr(row);
1492
+ });
1493
+ }
1494
+ exports.loadCustomEdges = loadCustomEdges;
1495
+ async function loadEgesInfo(options, id2) {
1496
+ const { id1, edgeType } = options;
1105
1497
  const edgeData = await loadEdgeData(edgeType);
1106
1498
  if (!edgeData) {
1107
1499
  throw new Error(`error loading edge data for ${edgeType}`);
1108
1500
  }
1109
- const defaultOptions = defaultEdgeQueryOptions(id1, edgeType);
1501
+ const defaultOptions = defaultEdgeQueryOptions(id1, edgeType, id2);
1110
1502
  let cls = defaultOptions.clause;
1111
1503
  if (options.queryOptions?.clause) {
1112
1504
  cls = clause.And(cls, options.queryOptions.clause);
1113
1505
  }
1114
- const rows = await loadRows({
1506
+ return {
1507
+ ...getEdgeClauseAndFields(cls, options),
1508
+ defaultOptions,
1115
1509
  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
- });
1510
+ };
1125
1511
  }
1126
- exports.loadCustomEdges = loadCustomEdges;
1127
1512
  async function loadUniqueEdge(options) {
1128
1513
  const { id1, edgeType, context } = options;
1129
1514
  const edgeData = await loadEdgeData(edgeType);
1130
1515
  if (!edgeData) {
1131
1516
  throw new Error(`error loading edge data for ${edgeType}`);
1132
1517
  }
1518
+ const { cls, fields } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
1133
1519
  const row = await loadRow({
1134
1520
  tableName: edgeData.edgeTable,
1135
- fields: edgeFields,
1136
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1521
+ fields: fields,
1522
+ clause: cls,
1137
1523
  context,
1138
1524
  });
1139
1525
  if (!row) {
@@ -1160,21 +1546,28 @@ async function loadRawEdgeCountX(options) {
1160
1546
  if (!edgeData) {
1161
1547
  throw new Error(`error loading edge data for ${edgeType}`);
1162
1548
  }
1549
+ const { cls } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
1163
1550
  const row = await loadRowX({
1164
1551
  tableName: edgeData.edgeTable,
1165
1552
  // sqlite needs as count otherwise it returns count(1)
1166
1553
  fields: ["count(1) as count"],
1167
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1554
+ clause: cls,
1168
1555
  context,
1169
1556
  });
1170
1557
  return parseInt(row["count"], 10) || 0;
1171
1558
  }
1172
1559
  exports.loadRawEdgeCountX = loadRawEdgeCountX;
1173
1560
  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);
1561
+ const { cls: actualClause, fields, tableName, } = await loadEgesInfo(options, options.id2);
1562
+ const row = await loadRow({
1563
+ tableName,
1564
+ fields: fields,
1565
+ clause: actualClause,
1566
+ context: options.context,
1567
+ });
1568
+ if (row) {
1569
+ return new options.ctr(row);
1570
+ }
1178
1571
  }
1179
1572
  exports.loadEdgeForID2 = loadEdgeForID2;
1180
1573
  async function loadNodesByEdge(viewer, id1, edgeType, options) {
@@ -1190,19 +1583,20 @@ async function loadNodesByEdge(viewer, id1, edgeType, options) {
1190
1583
  }
1191
1584
  exports.loadNodesByEdge = loadNodesByEdge;
1192
1585
  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);
1586
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
1587
+ return r instanceof Error ? null : r;
1198
1588
  }
1199
1589
  exports.applyPrivacyPolicyForRow = applyPrivacyPolicyForRow;
1590
+ async function applyPrivacyPolicyForRowImpl(viewer, options, row) {
1591
+ const ent = new options.ent(viewer, row);
1592
+ return applyPrivacyPolicyForEnt(viewer, ent, row, options);
1593
+ }
1200
1594
  async function applyPrivacyPolicyForRowX(viewer, options, row) {
1201
1595
  const ent = new options.ent(viewer, row);
1202
1596
  return await applyPrivacyPolicyForEntX(viewer, ent, row, options);
1203
1597
  }
1204
- exports.applyPrivacyPolicyForRowX = applyPrivacyPolicyForRowX;
1205
- async function applyPrivacyPolicyForRows(viewer, rows, options) {
1598
+ // deprecated. doesn't use entcache
1599
+ async function applyPrivacyPolicyForRowsDeprecated(viewer, rows, options) {
1206
1600
  let m = new Map();
1207
1601
  // apply privacy logic
1208
1602
  await Promise.all(rows.map(async (row) => {
@@ -1213,27 +1607,62 @@ async function applyPrivacyPolicyForRows(viewer, rows, options) {
1213
1607
  }));
1214
1608
  return m;
1215
1609
  }
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];
1610
+ async function applyPrivacyPolicyForRows(viewer, rows, options) {
1611
+ const result = new Array(rows.length);
1612
+ if (!rows.length) {
1613
+ return [];
1614
+ }
1615
+ const entLoader = getEntLoader(viewer, options);
1616
+ await Promise.all(rows.map(async (row, idx) => {
1617
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options, entLoader);
1618
+ if (r instanceof ErrorWrapper) {
1619
+ return;
1620
+ }
1621
+ result[idx] = r;
1622
+ }));
1623
+ // filter ents that aren't visible because of privacy
1624
+ return result.filter((r) => r !== undefined);
1226
1625
  }
1626
+ exports.applyPrivacyPolicyForRows = applyPrivacyPolicyForRows;
1227
1627
  // given a viewer, an id pair, and a map of edgeEnum to EdgeType
1228
1628
  // return the edgeEnum that's set in the group
1229
1629
  async function getEdgeTypeInGroup(viewer, id1, id2, m) {
1230
1630
  let promises = [];
1231
- for (const [k, v] of m) {
1232
- promises.push(loadEdgeWithConst(viewer, id1, id2, k, v));
1631
+ const edgeDatas = await loadEdgeDatas(...Array.from(m.values()));
1632
+ let tableToEdgeEnumMap = new Map();
1633
+ for (const [edgeEnum, edgeType] of m) {
1634
+ const edgeData = edgeDatas.get(edgeType);
1635
+ if (!edgeData) {
1636
+ throw new Error(`could not load edge data for '${edgeType}'`);
1637
+ }
1638
+ const l = tableToEdgeEnumMap.get(edgeData.edgeTable) ?? [];
1639
+ l.push(edgeEnum);
1640
+ tableToEdgeEnumMap.set(edgeData.edgeTable, l);
1233
1641
  }
1642
+ tableToEdgeEnumMap.forEach((edgeEnums, tableName) => {
1643
+ promises.push((async () => {
1644
+ const edgeTypes = edgeEnums.map((edgeEnum) => m.get(edgeEnum));
1645
+ const { cls, fields } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.In("edge_type", edgeTypes), clause.Eq("id2", id2)), {});
1646
+ const rows = await loadRows({
1647
+ tableName,
1648
+ fields,
1649
+ clause: cls,
1650
+ context: viewer.context,
1651
+ });
1652
+ const row = rows[0];
1653
+ if (row) {
1654
+ const edgeType = row.edge_type;
1655
+ for (const [k, v] of m) {
1656
+ if (v === edgeType) {
1657
+ return [k, new AssocEdge(row)];
1658
+ }
1659
+ }
1660
+ }
1661
+ })());
1662
+ });
1234
1663
  const results = await Promise.all(promises);
1235
1664
  for (const res of results) {
1236
- if (res[1]) {
1665
+ if (res && res[1]) {
1237
1666
  return [res[0], res[1]];
1238
1667
  }
1239
1668
  }