@snowtop/ent 0.1.0-alpha12 → 0.1.0-alpha120

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 (164) hide show
  1. package/action/action.d.ts +37 -31
  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 +33 -14
  10. package/action/orchestrator.js +251 -54
  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 +60 -37
  18. package/core/base.js +7 -1
  19. package/core/clause.d.ts +84 -40
  20. package/core/clause.js +358 -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 +641 -193
  33. package/core/loaders/assoc_count_loader.d.ts +3 -2
  34. package/core/loaders/assoc_count_loader.js +10 -2
  35. package/core/loaders/assoc_edge_loader.d.ts +2 -2
  36. package/core/loaders/assoc_edge_loader.js +8 -11
  37. package/core/loaders/index.d.ts +1 -1
  38. package/core/loaders/index.js +1 -3
  39. package/core/loaders/index_loader.d.ts +3 -3
  40. package/core/loaders/loader.d.ts +2 -2
  41. package/core/loaders/loader.js +5 -5
  42. package/core/loaders/object_loader.d.ts +11 -10
  43. package/core/loaders/object_loader.js +70 -60
  44. package/core/loaders/query_loader.d.ts +7 -13
  45. package/core/loaders/query_loader.js +52 -11
  46. package/core/loaders/raw_count_loader.d.ts +2 -2
  47. package/core/loaders/raw_count_loader.js +5 -1
  48. package/core/logger.d.ts +1 -1
  49. package/core/logger.js +1 -0
  50. package/core/privacy.d.ts +25 -24
  51. package/core/privacy.js +21 -25
  52. package/core/query/assoc_query.d.ts +7 -6
  53. package/core/query/assoc_query.js +9 -1
  54. package/core/query/custom_clause_query.d.ts +27 -0
  55. package/core/query/custom_clause_query.js +84 -0
  56. package/core/query/custom_query.d.ts +20 -5
  57. package/core/query/custom_query.js +87 -12
  58. package/core/query/index.d.ts +1 -0
  59. package/core/query/index.js +3 -1
  60. package/core/query/query.d.ts +8 -4
  61. package/core/query/query.js +101 -53
  62. package/core/query/shared_assoc_test.d.ts +2 -1
  63. package/core/query/shared_assoc_test.js +35 -45
  64. package/core/query/shared_test.d.ts +8 -1
  65. package/core/query/shared_test.js +469 -236
  66. package/core/viewer.d.ts +3 -3
  67. package/core/viewer.js +1 -1
  68. package/graphql/graphql.d.ts +15 -7
  69. package/graphql/graphql.js +23 -7
  70. package/graphql/index.d.ts +1 -1
  71. package/graphql/index.js +3 -4
  72. package/graphql/query/connection_type.d.ts +9 -9
  73. package/graphql/query/edge_connection.d.ts +9 -9
  74. package/graphql/query/page_info.d.ts +1 -1
  75. package/graphql/query/shared_assoc_test.js +1 -1
  76. package/graphql/query/shared_edge_connection.js +1 -19
  77. package/graphql/scalars/orderby_direction.d.ts +2 -0
  78. package/graphql/scalars/orderby_direction.js +15 -0
  79. package/imports/index.d.ts +6 -1
  80. package/imports/index.js +19 -4
  81. package/index.d.ts +12 -5
  82. package/index.js +20 -7
  83. package/package.json +17 -16
  84. package/parse_schema/parse.d.ts +30 -9
  85. package/parse_schema/parse.js +145 -12
  86. package/schema/base_schema.d.ts +5 -3
  87. package/schema/base_schema.js +6 -0
  88. package/schema/field.d.ts +75 -20
  89. package/schema/field.js +175 -69
  90. package/schema/index.d.ts +2 -2
  91. package/schema/index.js +5 -1
  92. package/schema/json_field.d.ts +16 -4
  93. package/schema/json_field.js +32 -2
  94. package/schema/schema.d.ts +87 -20
  95. package/schema/schema.js +13 -14
  96. package/schema/struct_field.d.ts +11 -1
  97. package/schema/struct_field.js +57 -21
  98. package/scripts/custom_compiler.js +10 -6
  99. package/scripts/custom_graphql.js +124 -31
  100. package/scripts/migrate_v0.1.js +36 -0
  101. package/scripts/move_types.js +117 -0
  102. package/scripts/read_schema.js +20 -5
  103. package/testutils/action/complex_schemas.d.ts +69 -0
  104. package/testutils/action/complex_schemas.js +398 -0
  105. package/testutils/builder.d.ts +43 -47
  106. package/testutils/builder.js +76 -49
  107. package/testutils/db/fixture.d.ts +10 -0
  108. package/testutils/db/fixture.js +26 -0
  109. package/testutils/db/{test_db.d.ts → temp_db.d.ts} +24 -8
  110. package/testutils/db/{test_db.js → temp_db.js} +182 -45
  111. package/testutils/db/value.d.ts +7 -0
  112. package/testutils/db/value.js +251 -0
  113. package/testutils/db_mock.d.ts +16 -4
  114. package/testutils/db_mock.js +51 -6
  115. package/testutils/db_time_zone.d.ts +4 -0
  116. package/testutils/db_time_zone.js +41 -0
  117. package/testutils/ent-graphql-tests/index.d.ts +7 -1
  118. package/testutils/ent-graphql-tests/index.js +52 -23
  119. package/testutils/fake_data/const.d.ts +2 -1
  120. package/testutils/fake_data/const.js +3 -0
  121. package/testutils/fake_data/fake_contact.d.ts +8 -4
  122. package/testutils/fake_data/fake_contact.js +15 -8
  123. package/testutils/fake_data/fake_event.d.ts +5 -2
  124. package/testutils/fake_data/fake_event.js +9 -7
  125. package/testutils/fake_data/fake_tag.d.ts +36 -0
  126. package/testutils/fake_data/fake_tag.js +89 -0
  127. package/testutils/fake_data/fake_user.d.ts +10 -7
  128. package/testutils/fake_data/fake_user.js +18 -16
  129. package/testutils/fake_data/index.js +5 -1
  130. package/testutils/fake_data/internal.d.ts +2 -0
  131. package/testutils/fake_data/internal.js +7 -1
  132. package/testutils/fake_data/tag_query.d.ts +13 -0
  133. package/testutils/fake_data/tag_query.js +43 -0
  134. package/testutils/fake_data/test_helpers.d.ts +11 -4
  135. package/testutils/fake_data/test_helpers.js +28 -12
  136. package/testutils/fake_data/user_query.d.ts +13 -6
  137. package/testutils/fake_data/user_query.js +54 -22
  138. package/testutils/fake_log.d.ts +3 -3
  139. package/testutils/parse_sql.d.ts +6 -0
  140. package/testutils/parse_sql.js +16 -2
  141. package/testutils/test_edge_global_schema.d.ts +15 -0
  142. package/testutils/test_edge_global_schema.js +62 -0
  143. package/testutils/write.d.ts +2 -2
  144. package/testutils/write.js +33 -7
  145. package/tsc/ast.d.ts +25 -2
  146. package/tsc/ast.js +141 -17
  147. package/tsc/compilerOptions.js +5 -1
  148. package/tsc/move_generated.d.ts +1 -0
  149. package/tsc/move_generated.js +164 -0
  150. package/tsc/transform.d.ts +22 -0
  151. package/tsc/transform.js +181 -0
  152. package/tsc/transform_action.d.ts +22 -0
  153. package/tsc/transform_action.js +183 -0
  154. package/tsc/transform_ent.d.ts +17 -0
  155. package/tsc/transform_ent.js +60 -0
  156. package/tsc/transform_schema.d.ts +27 -0
  157. package/{scripts → tsc}/transform_schema.js +146 -117
  158. package/graphql/enums.d.ts +0 -3
  159. package/graphql/enums.js +0 -25
  160. package/scripts/move_generated.js +0 -142
  161. package/scripts/transform_code.js +0 -113
  162. package/scripts/transform_schema.d.ts +0 -1
  163. /package/scripts/{move_generated.d.ts → migrate_v0.1.d.ts} +0 -0
  164. /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,15 @@ 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.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.__hasGlobalSchema = exports.clearGlobalSchema = exports.setGlobalSchema = 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 = exports.loadNodesByEdge = exports.loadEdgeForID2 = exports.loadRawEdgeCountX = 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/");
32
38
  // TODO kill this and createDataLoader
33
39
  class cacheMap {
34
40
  constructor(options) {
@@ -38,7 +44,7 @@ class cacheMap {
38
44
  get(key) {
39
45
  const ret = this.m.get(key);
40
46
  if (ret) {
41
- (0, logger_1.log)("query", {
47
+ (0, logger_1.log)("cache", {
42
48
  "dataloader-cache-hit": key,
43
49
  "tableName": this.options.tableName,
44
50
  });
@@ -55,12 +61,41 @@ class cacheMap {
55
61
  return this.m.clear();
56
62
  }
57
63
  }
64
+ class entCacheMap {
65
+ constructor(viewer, options) {
66
+ this.viewer = viewer;
67
+ this.options = options;
68
+ this.m = new Map();
69
+ this.logEnabled = false;
70
+ this.logEnabled = (0, logger_1.logEnabled)("cache");
71
+ }
72
+ get(id) {
73
+ const ret = this.m.get(id);
74
+ if (this.logEnabled && ret) {
75
+ const key = getEntKey(this.viewer, id, this.options);
76
+ (0, logger_1.log)("cache", {
77
+ "ent-cache-hit": key,
78
+ });
79
+ }
80
+ return ret;
81
+ }
82
+ set(key, value) {
83
+ return this.m.set(key, value);
84
+ }
85
+ delete(key) {
86
+ return this.m.delete(key);
87
+ }
88
+ clear() {
89
+ return this.m.clear();
90
+ }
91
+ }
58
92
  function createDataLoader(options) {
59
93
  const loaderOptions = {};
60
94
  // if query logging is enabled, we should log what's happening with loader
61
95
  if ((0, logger_1.logEnabled)("query")) {
62
96
  loaderOptions.cacheMap = new cacheMap(options);
63
97
  }
98
+ // something here brokwn with strict:true
64
99
  return new dataloader_1.default(async (ids) => {
65
100
  if (!ids.length) {
66
101
  return [];
@@ -84,28 +119,151 @@ function createDataLoader(options) {
84
119
  return result;
85
120
  }, loaderOptions);
86
121
  }
87
- // Ent accessors
122
+ // used to wrap errors that would eventually be thrown in ents
123
+ // not an Error because DataLoader automatically rejects that
124
+ class ErrorWrapper {
125
+ constructor(error) {
126
+ this.error = error;
127
+ }
128
+ }
129
+ function createEntLoader(viewer, options, map) {
130
+ // share the cache across loaders even if we create a new instance
131
+ const loaderOptions = {};
132
+ loaderOptions.cacheMap = map;
133
+ return new dataloader_1.default(async (ids) => {
134
+ if (!ids.length) {
135
+ return [];
136
+ }
137
+ let result = [];
138
+ const tableName = options.loaderFactory.options?.tableName;
139
+ const loader = options.loaderFactory.createLoader(viewer.context);
140
+ const rows = await loader.loadMany(ids);
141
+ // this is a loader which should return the same order based on passed-in ids
142
+ // so let's depend on that...
143
+ for (let idx = 0; idx < rows.length; idx++) {
144
+ const row = rows[idx];
145
+ // db error
146
+ if (row instanceof Error) {
147
+ result[idx] = row;
148
+ continue;
149
+ }
150
+ else if (!row) {
151
+ if (tableName) {
152
+ result[idx] = new ErrorWrapper(new Error(`couldn't find row for value ${ids[idx]} in table ${tableName}`));
153
+ }
154
+ else {
155
+ result[idx] = new ErrorWrapper(new Error(`couldn't find row for value ${ids[idx]}`));
156
+ }
157
+ }
158
+ else {
159
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
160
+ if (r instanceof Error) {
161
+ result[idx] = new ErrorWrapper(r);
162
+ }
163
+ else {
164
+ result[idx] = r;
165
+ }
166
+ }
167
+ }
168
+ return result;
169
+ }, loaderOptions);
170
+ }
171
+ class EntLoader {
172
+ constructor(viewer, options) {
173
+ this.viewer = viewer;
174
+ this.options = options;
175
+ this.map = new entCacheMap(viewer, options);
176
+ this.loader = createEntLoader(this.viewer, this.options, this.map);
177
+ }
178
+ getMap() {
179
+ return this.map;
180
+ }
181
+ async load(id) {
182
+ return this.loader.load(id);
183
+ }
184
+ async loadMany(ids) {
185
+ return this.loader.loadMany(ids);
186
+ }
187
+ prime(id, ent) {
188
+ this.loader.prime(id, ent);
189
+ }
190
+ clear(id) {
191
+ this.loader.clear(id);
192
+ }
193
+ clearAll() {
194
+ this.loader.clearAll();
195
+ }
196
+ }
197
+ function getEntLoader(viewer, options) {
198
+ if (!viewer.context?.cache) {
199
+ return new EntLoader(viewer, options);
200
+ }
201
+ const name = `ent-loader:${viewer.instanceKey()}:${options.loaderFactory.name}`;
202
+ return viewer.context.cache.getLoaderWithLoadMany(name, () => new EntLoader(viewer, options));
203
+ }
204
+ function getEntKey(viewer, id, options) {
205
+ return `${viewer.instanceKey()}:${options.loaderFactory.name}:${id}`;
206
+ }
207
+ exports.getEntKey = getEntKey;
88
208
  async function loadEnt(viewer, id, options) {
89
- const row = await options.loaderFactory.createLoader(viewer.context).load(id);
90
- return await applyPrivacyPolicyForRow(viewer, options, row);
209
+ if (typeof id !== "string" &&
210
+ typeof id !== "number" &&
211
+ typeof id !== "bigint") {
212
+ throw new Error(`invalid id ${id} passed to loadEnt`);
213
+ }
214
+ const r = await getEntLoader(viewer, options).load(id);
215
+ return r instanceof ErrorWrapper ? null : r;
91
216
  }
92
217
  exports.loadEnt = loadEnt;
218
+ async function applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options,
219
+ // can pass in loader when calling this for multi-id cases...
220
+ loader) {
221
+ if (!loader) {
222
+ loader = getEntLoader(viewer, options);
223
+ }
224
+ // TODO every row.id needs to be audited...
225
+ // https://github.com/lolopinto/ent/issues/1064
226
+ const id = row.id;
227
+ // we should check the ent loader cache to see if this is already there
228
+ // 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
229
+ const result = loader.getMap().get(id);
230
+ if (result !== undefined) {
231
+ return result;
232
+ }
233
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
234
+ if (r instanceof Error) {
235
+ loader.prime(id, new ErrorWrapper(r));
236
+ return new ErrorWrapper(r);
237
+ }
238
+ else {
239
+ loader.prime(id, r);
240
+ return r;
241
+ }
242
+ }
93
243
  // this is the same implementation-wise (right now) as loadEnt. it's just clearer that it's not loaded via ID.
94
244
  // used for load via email address etc
95
245
  async function loadEntViaKey(viewer, key, options) {
96
246
  const row = await options.loaderFactory
97
247
  .createLoader(viewer.context)
98
248
  .load(key);
99
- return await applyPrivacyPolicyForRow(viewer, options, row);
249
+ if (!row) {
250
+ return null;
251
+ }
252
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options);
253
+ return r instanceof ErrorWrapper ? null : r;
100
254
  }
101
255
  exports.loadEntViaKey = loadEntViaKey;
102
256
  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}`);
257
+ if (typeof id !== "string" &&
258
+ typeof id !== "number" &&
259
+ typeof id !== "bigint") {
260
+ throw new Error(`invalid id ${id} passed to loadEntX`);
107
261
  }
108
- return await applyPrivacyPolicyForRowX(viewer, options, row);
262
+ const r = await getEntLoader(viewer, options).load(id);
263
+ if (r instanceof ErrorWrapper) {
264
+ throw r.error;
265
+ }
266
+ return r;
109
267
  }
110
268
  exports.loadEntX = loadEntX;
111
269
  async function loadEntXViaKey(viewer, key, options) {
@@ -116,9 +274,16 @@ async function loadEntXViaKey(viewer, key, options) {
116
274
  // todo make this better
117
275
  throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${key}`);
118
276
  }
119
- return await applyPrivacyPolicyForRowX(viewer, options, row);
277
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options);
278
+ if (r instanceof ErrorWrapper) {
279
+ throw r.error;
280
+ }
281
+ return r;
120
282
  }
121
283
  exports.loadEntXViaKey = loadEntXViaKey;
284
+ /**
285
+ * @deprecated use loadCustomEnts
286
+ */
122
287
  async function loadEntFromClause(viewer, options, clause) {
123
288
  const rowOptions = {
124
289
  ...options,
@@ -126,12 +291,18 @@ async function loadEntFromClause(viewer, options, clause) {
126
291
  context: viewer.context,
127
292
  };
128
293
  const row = await loadRow(rowOptions);
129
- return await applyPrivacyPolicyForRow(viewer, options, row);
294
+ if (row === null) {
295
+ return null;
296
+ }
297
+ return applyPrivacyPolicyForRow(viewer, options, row);
130
298
  }
131
299
  exports.loadEntFromClause = loadEntFromClause;
132
300
  // same as loadEntFromClause
133
301
  // only works for ents where primary key is "id"
134
302
  // use loadEnt with a loaderFactory if different
303
+ /**
304
+ * @deprecated use loadCustomEnts
305
+ */
135
306
  async function loadEntXFromClause(viewer, options, clause) {
136
307
  const rowOptions = {
137
308
  ...options,
@@ -146,37 +317,19 @@ async function loadEnts(viewer, options, ...ids) {
146
317
  if (!ids.length) {
147
318
  return new Map();
148
319
  }
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
320
+ // result
158
321
  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);
322
+ const ret = await getEntLoader(viewer, options).loadMany(ids);
323
+ for (const r of ret) {
324
+ if (r instanceof Error) {
325
+ throw r;
169
326
  }
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);
327
+ if (r instanceof ErrorWrapper) {
328
+ continue;
329
+ }
330
+ m.set(r.id, r);
176
331
  }
177
332
  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
333
  }
181
334
  exports.loadEnts = loadEnts;
182
335
  // calls loadEnts and returns the results sorted in the order they were passed in
@@ -195,6 +348,9 @@ async function loadEntsList(viewer, options, ...ids) {
195
348
  exports.loadEntsList = loadEntsList;
196
349
  // we return a map here so that any sorting for queries that exist
197
350
  // can be done in O(N) time
351
+ /**
352
+ * @deperecated use loadCustomEnts
353
+ */
198
354
  async function loadEntsFromClause(viewer, clause, options) {
199
355
  const rowOptions = {
200
356
  ...options,
@@ -202,65 +358,139 @@ async function loadEntsFromClause(viewer, clause, options) {
202
358
  context: viewer.context,
203
359
  };
204
360
  const rows = await loadRows(rowOptions);
205
- return await applyPrivacyPolicyForRows(viewer, rows, options);
361
+ return applyPrivacyPolicyForRowsDeprecated(viewer, rows, options);
206
362
  }
207
363
  exports.loadEntsFromClause = loadEntsFromClause;
208
364
  async function loadCustomEnts(viewer, options, query) {
209
365
  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);
366
+ return applyPrivacyPolicyForRows(viewer, rows, options);
220
367
  }
221
368
  exports.loadCustomEnts = loadCustomEnts;
222
369
  function isClause(opts) {
223
370
  const cls = opts;
224
371
  return cls.clause !== undefined && cls.values !== undefined;
225
372
  }
226
- function isRawQuery(opts) {
373
+ function isParameterizedQuery(opts) {
227
374
  return opts.query !== undefined;
228
375
  }
376
+ /**
377
+ * Note that if there's default read transformations (e.g. soft delete) and a clause is passed in
378
+ * either as Clause or QueryDataOptions without {disableTransformations: true}, the default transformation
379
+ * (e.g. soft delete) is applied.
380
+ *
381
+ * Passing a full SQL string or Paramterized SQL string doesn't apply it and the given string is sent to the
382
+ * database as written.
383
+ *
384
+ * e.g.
385
+ * Foo.loadCustom(opts, 'SELECT * FROM foo') // doesn't change the query
386
+ * Foo.loadCustom(opts, { query: 'SELECT * FROM foo WHERE id = ?', values: [1]}) // doesn't change the query
387
+ * Foo.loadCustom(opts, query.Eq('time', Date.now())) // changes the query
388
+ * Foo.loadCustom(opts, {
389
+ * clause: query.LessEq('time', Date.now()),
390
+ * limit: 100,
391
+ * orderby: 'time',
392
+ * }) // changes the query
393
+ * Foo.loadCustom(opts, {
394
+ * clause: query.LessEq('time', Date.now()),
395
+ * limit: 100,
396
+ * orderby: 'time',
397
+ * disableTransformations: false
398
+ * }) // doesn't change the query
399
+ */
229
400
  async function loadCustomData(options, query, context) {
401
+ const rows = await loadCustomDataImpl(options, query, context);
402
+ // prime the data so that subsequent fetches of the row with this id are a cache hit.
403
+ if (options.prime) {
404
+ const loader = options.loaderFactory.createLoader(context);
405
+ if (isPrimableLoader(loader) && loader.primeAll !== undefined) {
406
+ for (const row of rows) {
407
+ loader.primeAll(row);
408
+ }
409
+ }
410
+ }
411
+ return rows;
412
+ }
413
+ exports.loadCustomData = loadCustomData;
414
+ // NOTE: if you use a raw query or paramterized query with this,
415
+ // you should use `SELECT count(*) as count...`
416
+ async function loadCustomCount(options, query, context) {
417
+ // TODO also need to loaderify this in case we're querying for this a lot...
418
+ const rows = await loadCustomDataImpl({
419
+ ...options,
420
+ fields: ["count(1) as count"],
421
+ }, query, context);
422
+ if (rows.length) {
423
+ return parseInt(rows[0].count);
424
+ }
425
+ return 0;
426
+ }
427
+ exports.loadCustomCount = loadCustomCount;
428
+ function isPrimableLoader(loader) {
429
+ return loader != undefined;
430
+ }
431
+ async function loadCustomDataImpl(options, query, context) {
432
+ function getClause(cls) {
433
+ let optClause = options.loaderFactory?.options?.clause;
434
+ if (typeof optClause === "function") {
435
+ optClause = optClause();
436
+ }
437
+ if (!optClause) {
438
+ return cls;
439
+ }
440
+ // @ts-expect-error string|ID mismatch
441
+ return clause.And(cls, optClause);
442
+ }
230
443
  if (typeof query === "string") {
231
444
  // no caching, perform raw query
232
- return await performRawQuery(query, [], []);
445
+ return performRawQuery(query, [], []);
446
+ // @ts-ignore
233
447
  }
234
448
  else if (isClause(query)) {
449
+ // if a Clause is passed in and we have a default clause
450
+ // associated with the query, pass that in
451
+ // if we want to disableTransformations, need to indicate that with
452
+ // disableTransformations option
235
453
  // this will have rudimentary caching but nothing crazy
236
- return await loadRows({
454
+ return loadRows({
237
455
  ...options,
238
- clause: query,
456
+ // @ts-ignore
457
+ clause: getClause(query),
239
458
  context: context,
240
459
  });
241
460
  }
242
- else if (isRawQuery(query)) {
461
+ else if (isParameterizedQuery(query)) {
243
462
  // no caching, perform raw query
244
- return await performRawQuery(query.query, query.values || [], query.logValues);
463
+ return performRawQuery(query.query, query.values || [], query.logValues);
245
464
  }
246
465
  else {
466
+ let cls = query.clause;
467
+ if (!query.disableTransformations) {
468
+ // @ts-ignore
469
+ cls = getClause(cls);
470
+ }
247
471
  // this will have rudimentary caching but nothing crazy
248
- return await loadRows({
472
+ return loadRows({
249
473
  ...query,
250
474
  ...options,
251
475
  context: context,
476
+ clause: cls,
252
477
  });
253
478
  }
254
479
  }
255
- exports.loadCustomData = loadCustomData;
256
480
  // Derived ents
481
+ // no ent caching
257
482
  async function loadDerivedEnt(viewer, data, loader) {
258
483
  const ent = new loader(viewer, data);
259
- return await applyPrivacyPolicyForEnt(viewer, ent, data, {
484
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, {
260
485
  ent: loader,
261
486
  });
487
+ if (r instanceof Error) {
488
+ return null;
489
+ }
490
+ return r;
262
491
  }
263
492
  exports.loadDerivedEnt = loadDerivedEnt;
493
+ // won't have caching yet either
264
494
  async function loadDerivedEntX(viewer, data, loader) {
265
495
  const ent = new loader(viewer, data);
266
496
  return await applyPrivacyPolicyForEntX(viewer, ent, data, { ent: loader });
@@ -269,19 +499,21 @@ exports.loadDerivedEntX = loadDerivedEntX;
269
499
  // everything calls into this two so should be fine
270
500
  // TODO is there a smarter way to not instantiate two objects here?
271
501
  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
- }
502
+ const error = await (0, privacy_1.applyPrivacyPolicyImpl)(viewer, ent.getPrivacyPolicy(), ent);
503
+ if (error === null) {
277
504
  return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
278
505
  }
279
- return null;
506
+ return error;
280
507
  }
281
508
  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);
509
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, options);
510
+ if (r instanceof Error) {
511
+ throw r;
512
+ }
513
+ if (r === null) {
514
+ throw new Error(`couldn't apply privacyPoliy for ent ${ent.id}`);
515
+ }
516
+ return r;
285
517
  }
286
518
  async function doFieldPrivacy(viewer, ent, data, options) {
287
519
  if (!options.fieldPrivacy) {
@@ -289,13 +521,16 @@ async function doFieldPrivacy(viewer, ent, data, options) {
289
521
  }
290
522
  const promises = [];
291
523
  let somethingChanged = false;
524
+ const origData = {
525
+ ...data,
526
+ };
292
527
  for (const [k, policy] of options.fieldPrivacy) {
528
+ const curr = data[k];
529
+ if (curr === null || curr === undefined) {
530
+ continue;
531
+ }
293
532
  promises.push((async () => {
294
533
  // 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
534
  const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
300
535
  if (!r) {
301
536
  data[k] = null;
@@ -306,8 +541,11 @@ async function doFieldPrivacy(viewer, ent, data, options) {
306
541
  await Promise.all(promises);
307
542
  if (somethingChanged) {
308
543
  // have to create new instance
309
- return new options.ent(viewer, data);
544
+ const ent = new options.ent(viewer, data);
545
+ ent.__setRawDBData(origData);
546
+ return ent;
310
547
  }
548
+ ent.__setRawDBData(origData);
311
549
  return ent;
312
550
  }
313
551
  function logQuery(query, logValues) {
@@ -317,6 +555,7 @@ function logQuery(query, logValues) {
317
555
  });
318
556
  (0, logger_1.logTrace)();
319
557
  }
558
+ exports.logQuery = logQuery;
320
559
  // TODO long term figure out if this API should be exposed
321
560
  async function loadRowX(options) {
322
561
  const result = await loadRow(options);
@@ -339,29 +578,26 @@ async function loadRow(options) {
339
578
  }
340
579
  const query = buildQuery(options);
341
580
  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]);
581
+ const pool = db_1.default.getInstance().getPool();
582
+ const res = await pool.query(query, options.clause.values());
583
+ if (res.rowCount != 1) {
584
+ if (res.rowCount > 1) {
585
+ (0, logger_1.log)("error", "got more than one row for query " + query);
354
586
  }
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
587
  return null;
362
588
  }
589
+ // put the row in the cache...
590
+ if (cache) {
591
+ cache.primeCache(options, res.rows[0]);
592
+ }
593
+ return res.rows[0];
363
594
  }
364
595
  exports.loadRow = loadRow;
596
+ var _logQueryWithError = false;
597
+ function ___setLogQueryErrorWithError(val) {
598
+ _logQueryWithError = val || false;
599
+ }
600
+ exports.___setLogQueryErrorWithError = ___setLogQueryErrorWithError;
365
601
  // this always goes to the db, no cache, nothing
366
602
  async function performRawQuery(query, values, logValues) {
367
603
  const pool = db_1.default.getInstance().getPool();
@@ -371,9 +607,11 @@ async function performRawQuery(query, values, logValues) {
371
607
  return res.rows;
372
608
  }
373
609
  catch (e) {
374
- // TODO need to change every query to catch an error!
375
- (0, logger_1.log)("error", e);
376
- return [];
610
+ if (_logQueryWithError) {
611
+ const msg = e.message;
612
+ throw new Error(`error \`${msg}\` running query: \`${query}\` with values: \`${logValues}\``);
613
+ }
614
+ throw e;
377
615
  }
378
616
  }
379
617
  exports.performRawQuery = performRawQuery;
@@ -432,10 +670,41 @@ function buildGroupQuery(options) {
432
670
  ];
433
671
  }
434
672
  exports.buildGroupQuery = buildGroupQuery;
673
+ class RawQueryOperation {
674
+ constructor(queries) {
675
+ this.queries = queries;
676
+ }
677
+ async performWrite(queryer, context) {
678
+ for (const q of this.queries) {
679
+ if (typeof q === "string") {
680
+ logQuery(q, []);
681
+ await queryer.query(q);
682
+ }
683
+ else {
684
+ logQuery(q.query, q.logValues || []);
685
+ await queryer.query(q.query, q.values);
686
+ }
687
+ }
688
+ }
689
+ performWriteSync(queryer, context) {
690
+ for (const q of this.queries) {
691
+ if (typeof q === "string") {
692
+ logQuery(q, []);
693
+ queryer.execSync(q);
694
+ }
695
+ else {
696
+ logQuery(q.query, q.logValues || []);
697
+ queryer.execSync(q.query, q.values);
698
+ }
699
+ }
700
+ }
701
+ }
702
+ exports.RawQueryOperation = RawQueryOperation;
435
703
  class EditNodeOperation {
436
704
  constructor(options, existingEnt = null) {
437
705
  this.options = options;
438
706
  this.existingEnt = existingEnt;
707
+ this.row = null;
439
708
  this.placeholderID = options.placeholderID;
440
709
  }
441
710
  resolve(executor) {
@@ -471,9 +740,10 @@ class EditNodeOperation {
471
740
  if (this.hasData(options.fields)) {
472
741
  // even this with returning * may not always work if transformed...
473
742
  // we can have a transformed flag to see if it should be returned?
474
- this.row = await editRow(queryer, options, this.existingEnt.id, "RETURNING *");
743
+ this.row = await editRow(queryer, options, "RETURNING *");
475
744
  }
476
745
  else {
746
+ // @ts-ignore
477
747
  this.row = this.existingEnt["data"];
478
748
  }
479
749
  }
@@ -496,7 +766,8 @@ class EditNodeOperation {
496
766
  optionClause = opts.clause;
497
767
  }
498
768
  if (optionClause) {
499
- cls = clause.And(optionClause, cls);
769
+ // @ts-expect-error ID|string mismatch
770
+ cls = clause.And(cls, optionClause);
500
771
  }
501
772
  }
502
773
  const query = buildQuery({
@@ -520,10 +791,11 @@ class EditNodeOperation {
520
791
  };
521
792
  if (this.existingEnt) {
522
793
  if (this.hasData(this.options.fields)) {
523
- editRowSync(queryer, options, this.existingEnt.id, "RETURNING *");
794
+ editRowSync(queryer, options, "RETURNING *");
524
795
  this.reloadRow(queryer, this.existingEnt.id, options);
525
796
  }
526
797
  else {
798
+ // @ts-ignore
527
799
  this.row = this.existingEnt["data"];
528
800
  }
529
801
  }
@@ -544,8 +816,23 @@ class EditNodeOperation {
544
816
  }
545
817
  }
546
818
  exports.EditNodeOperation = EditNodeOperation;
819
+ let globalSchema;
820
+ function setGlobalSchema(val) {
821
+ globalSchema = val;
822
+ }
823
+ exports.setGlobalSchema = setGlobalSchema;
824
+ function clearGlobalSchema() {
825
+ globalSchema = undefined;
826
+ }
827
+ exports.clearGlobalSchema = clearGlobalSchema;
828
+ // used by tests. no guarantee will always exist
829
+ function __hasGlobalSchema() {
830
+ return globalSchema !== undefined;
831
+ }
832
+ exports.__hasGlobalSchema = __hasGlobalSchema;
547
833
  class EdgeOperation {
548
- constructor(edgeInput, options) {
834
+ constructor(builder, edgeInput, options) {
835
+ this.builder = builder;
549
836
  this.edgeInput = edgeInput;
550
837
  this.options = options;
551
838
  }
@@ -581,7 +868,31 @@ class EdgeOperation {
581
868
  }
582
869
  }
583
870
  getDeleteRowParams(edgeData, edge, context) {
871
+ let transformed = null;
872
+ let op = schema_1.SQLStatementOperation.Delete;
873
+ let updateData = null;
874
+ // TODO respect disableTransformations
875
+ if (globalSchema?.transformEdgeWrite) {
876
+ transformed = globalSchema.transformEdgeWrite({
877
+ op: schema_1.SQLStatementOperation.Delete,
878
+ edge,
879
+ });
880
+ if (transformed) {
881
+ op = transformed.op;
882
+ if (transformed.op === schema_1.SQLStatementOperation.Insert) {
883
+ throw new Error(`cannot currently transform a delete into an insert`);
884
+ }
885
+ if (transformed.op === schema_1.SQLStatementOperation.Update) {
886
+ if (!transformed.data) {
887
+ throw new Error(`cannot transform a delete into an update without providing data`);
888
+ }
889
+ updateData = transformed.data;
890
+ }
891
+ }
892
+ }
584
893
  return {
894
+ op,
895
+ updateData,
585
896
  options: {
586
897
  tableName: edgeData.edgeTable,
587
898
  context,
@@ -591,11 +902,36 @@ class EdgeOperation {
591
902
  }
592
903
  async performDeleteWrite(q, edgeData, edge, context) {
593
904
  const params = this.getDeleteRowParams(edgeData, edge, context);
594
- return deleteRows(q, params.options, params.clause);
905
+ if (params.op === schema_1.SQLStatementOperation.Delete) {
906
+ return deleteRows(q, params.options, params.clause);
907
+ }
908
+ else {
909
+ if (params.op !== schema_1.SQLStatementOperation.Update) {
910
+ throw new Error(`invalid operation ${params.op}`);
911
+ }
912
+ await editRow(q, {
913
+ tableName: params.options.tableName,
914
+ whereClause: params.clause,
915
+ fields: params.updateData,
916
+ fieldsToLog: params.updateData,
917
+ });
918
+ }
595
919
  }
596
920
  performDeleteWriteSync(q, edgeData, edge, context) {
597
921
  const params = this.getDeleteRowParams(edgeData, edge, context);
598
- return deleteRowsSync(q, params.options, params.clause);
922
+ if (params.op === schema_1.SQLStatementOperation.Delete) {
923
+ return deleteRowsSync(q, params.options, params.clause);
924
+ }
925
+ else {
926
+ if (params.op !== schema_1.SQLStatementOperation.Update) {
927
+ throw new Error(`invalid operation ${params.op}`);
928
+ }
929
+ editRowSync(q, {
930
+ tableName: params.options.tableName,
931
+ whereClause: params.clause,
932
+ fields: params.updateData,
933
+ });
934
+ }
599
935
  }
600
936
  getInsertRowParams(edgeData, edge, context) {
601
937
  const fields = {
@@ -614,6 +950,30 @@ class EdgeOperation {
614
950
  // maybe when actions exist?
615
951
  fields["time"] = new Date().toISOString();
616
952
  }
953
+ const onConflictFields = ["data"];
954
+ if (globalSchema?.extraEdgeFields) {
955
+ for (const name in globalSchema.extraEdgeFields) {
956
+ const f = globalSchema.extraEdgeFields[name];
957
+ if (f.defaultValueOnCreate) {
958
+ const storageKey = (0, schema_1.getStorageKey)(f, name);
959
+ fields[storageKey] = f.defaultValueOnCreate(this.builder, {});
960
+ // onconflict make sure we override the default values
961
+ // e.g. setting deleted_at = null for soft delete
962
+ onConflictFields.push(storageKey);
963
+ }
964
+ }
965
+ }
966
+ // TODO respect disableTransformations
967
+ let transformed = null;
968
+ if (globalSchema?.transformEdgeWrite) {
969
+ transformed = globalSchema.transformEdgeWrite({
970
+ op: schema_1.SQLStatementOperation.Insert,
971
+ edge,
972
+ });
973
+ if (transformed) {
974
+ throw new Error(`transforming an insert edge not currently supported`);
975
+ }
976
+ }
617
977
  return [
618
978
  {
619
979
  tableName: edgeData.edgeTable,
@@ -621,7 +981,9 @@ class EdgeOperation {
621
981
  fieldsToLog: fields,
622
982
  context,
623
983
  },
624
- "ON CONFLICT(id1, edge_type, id2) DO UPDATE SET data = EXCLUDED.data",
984
+ `ON CONFLICT(id1, edge_type, id2) DO UPDATE SET ${onConflictFields
985
+ .map((f) => `${f} = EXCLUDED.${f}`)
986
+ .join(", ")}`,
625
987
  ];
626
988
  }
627
989
  async performInsertWrite(q, edgeData, edge, context) {
@@ -658,7 +1020,7 @@ class EdgeOperation {
658
1020
  }
659
1021
  }
660
1022
  symmetricEdge() {
661
- return new EdgeOperation({
1023
+ return new EdgeOperation(this.builder, {
662
1024
  id1: this.edgeInput.id2,
663
1025
  id1Type: this.edgeInput.id2Type,
664
1026
  id2: this.edgeInput.id1,
@@ -674,7 +1036,7 @@ class EdgeOperation {
674
1036
  });
675
1037
  }
676
1038
  inverseEdge(edgeData) {
677
- return new EdgeOperation({
1039
+ return new EdgeOperation(this.builder, {
678
1040
  id1: this.edgeInput.id2,
679
1041
  id1Type: this.edgeInput.id2Type,
680
1042
  id2: this.edgeInput.id1,
@@ -742,7 +1104,7 @@ class EdgeOperation {
742
1104
  if (data) {
743
1105
  edge.data = data;
744
1106
  }
745
- return new EdgeOperation(edge, {
1107
+ return new EdgeOperation(builder, edge, {
746
1108
  operation: action_1.WriteOperation.Insert,
747
1109
  id2Placeholder,
748
1110
  id1Placeholder,
@@ -763,7 +1125,7 @@ class EdgeOperation {
763
1125
  if (data) {
764
1126
  edge.data = data;
765
1127
  }
766
- return new EdgeOperation(edge, {
1128
+ return new EdgeOperation(builder, edge, {
767
1129
  operation: action_1.WriteOperation.Insert,
768
1130
  id1Placeholder,
769
1131
  id2Placeholder,
@@ -781,7 +1143,7 @@ class EdgeOperation {
781
1143
  id2Type: "",
782
1144
  id1Type: "",
783
1145
  };
784
- return new EdgeOperation(edge, {
1146
+ return new EdgeOperation(builder, edge, {
785
1147
  operation: action_1.WriteOperation.Delete,
786
1148
  });
787
1149
  }
@@ -796,7 +1158,7 @@ class EdgeOperation {
796
1158
  id2Type: "",
797
1159
  id1Type: "",
798
1160
  };
799
- return new EdgeOperation(edge, {
1161
+ return new EdgeOperation(builder, edge, {
800
1162
  operation: action_1.WriteOperation.Delete,
801
1163
  });
802
1164
  }
@@ -808,24 +1170,26 @@ function isSyncQueryer(queryer) {
808
1170
  async function mutateRow(queryer, query, values, logValues, options) {
809
1171
  logQuery(query, logValues);
810
1172
  let cache = options.context?.cache;
1173
+ let res;
811
1174
  try {
812
- let res;
813
1175
  if (isSyncQueryer(queryer)) {
814
1176
  res = queryer.execSync(query, values);
815
1177
  }
816
1178
  else {
817
1179
  res = await queryer.exec(query, values);
818
1180
  }
819
- if (cache) {
820
- cache.clearCache();
1181
+ }
1182
+ catch (e) {
1183
+ if (_logQueryWithError) {
1184
+ const msg = e.message;
1185
+ throw new Error(`error \`${msg}\` running query: \`${query}\``);
821
1186
  }
822
- return res;
1187
+ throw e;
823
1188
  }
824
- catch (err) {
825
- // TODO:::why is this not rethrowing?
826
- (0, logger_1.log)("error", err);
827
- throw err;
1189
+ if (cache) {
1190
+ cache.clearCache();
828
1191
  }
1192
+ return res;
829
1193
  }
830
1194
  function mutateRowSync(queryer, query, values, logValues, options) {
831
1195
  logQuery(query, logValues);
@@ -837,10 +1201,12 @@ function mutateRowSync(queryer, query, values, logValues, options) {
837
1201
  }
838
1202
  return res;
839
1203
  }
840
- catch (err) {
841
- // TODO:::why is this not rethrowing?
842
- (0, logger_1.log)("error", err);
843
- throw err;
1204
+ catch (e) {
1205
+ if (_logQueryWithError) {
1206
+ const msg = e.message;
1207
+ throw new Error(`error \`${msg}\` running query: \`${query}\``);
1208
+ }
1209
+ throw e;
844
1210
  }
845
1211
  }
846
1212
  function buildInsertQuery(options, suffix) {
@@ -893,32 +1259,47 @@ function createRowSync(queryer, options, suffix) {
893
1259
  return null;
894
1260
  }
895
1261
  exports.createRowSync = createRowSync;
896
- function buildUpdateQuery(options, id, suffix) {
1262
+ function buildUpdateQuery(options, suffix) {
897
1263
  let valsString = [];
898
1264
  let values = [];
899
1265
  let logValues = [];
900
1266
  const dialect = db_1.default.getDialect();
901
1267
  let idx = 1;
902
1268
  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++;
1269
+ if (options.expressions && options.expressions.has(key)) {
1270
+ const cls = options.expressions.get(key);
1271
+ valsString.push(`${key} = ${cls.clause(idx)}`);
1272
+ // TODO need to test a clause with more than one value...
1273
+ const newVals = cls.values();
1274
+ idx += newVals.length;
1275
+ values.push(...newVals);
1276
+ logValues.push(...cls.logValues());
910
1277
  }
911
1278
  else {
912
- valsString.push(`${key} = ?`);
1279
+ const val = options.fields[key];
1280
+ values.push(val);
1281
+ if (options.fieldsToLog) {
1282
+ logValues.push(options.fieldsToLog[key]);
1283
+ }
1284
+ // TODO would be nice to use clause here. need update version of the queries so that
1285
+ // we don't have to handle dialect specifics here
1286
+ // can't use clause because of IS NULL
1287
+ // valsString.push(clause.Eq(key, val).clause(idx));
1288
+ if (dialect === db_1.Dialect.Postgres) {
1289
+ valsString.push(`${key} = $${idx}`);
1290
+ }
1291
+ else {
1292
+ valsString.push(`${key} = ?`);
1293
+ }
1294
+ idx++;
913
1295
  }
914
1296
  }
915
1297
  const vals = valsString.join(", ");
916
1298
  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} = ?`;
1299
+ query = query + options.whereClause.clause(idx);
1300
+ values.push(...options.whereClause.values());
1301
+ if (options.fieldsToLog) {
1302
+ logValues.push(...options.whereClause.logValues());
922
1303
  }
923
1304
  if (suffix) {
924
1305
  query = query + " " + suffix;
@@ -926,10 +1307,8 @@ function buildUpdateQuery(options, id, suffix) {
926
1307
  return [query, values, logValues];
927
1308
  }
928
1309
  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);
1310
+ async function editRow(queryer, options, suffix) {
1311
+ const [query, values, logValues] = buildUpdateQuery(options, suffix);
933
1312
  const res = await mutateRow(queryer, query, values, logValues, options);
934
1313
  if (res?.rowCount == 1) {
935
1314
  // for now assume id primary key
@@ -940,10 +1319,8 @@ async function editRow(queryer, options, id, suffix) {
940
1319
  return null;
941
1320
  }
942
1321
  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);
1322
+ function editRowSync(queryer, options, suffix) {
1323
+ const [query, values, logValues] = buildUpdateQuery(options, suffix);
947
1324
  const res = mutateRowSync(queryer, query, values, logValues, options);
948
1325
  if (res?.rowCount == 1) {
949
1326
  // for now assume id primary key
@@ -994,21 +1371,22 @@ class AssocEdge {
994
1371
  this.edgeType = data.edge_type;
995
1372
  this.time = data.time;
996
1373
  this.data = data.data;
1374
+ this.rawData = data;
1375
+ }
1376
+ __getRawData() {
1377
+ // incase there's extra db fields. useful for tests
1378
+ // in production, a subclass of this should be in use so we won't need this...
1379
+ return this.rawData;
997
1380
  }
998
1381
  getCursor() {
999
1382
  return getCursor({
1000
1383
  row: this,
1001
- col: "time",
1002
- conv: (t) => {
1003
- if (typeof t === "string") {
1004
- return Date.parse(t);
1005
- }
1006
- return t.getTime();
1007
- },
1384
+ col: "id2",
1008
1385
  });
1009
1386
  }
1010
1387
  }
1011
1388
  exports.AssocEdge = AssocEdge;
1389
+ // TODO eventually update this for sortCol time unique keys
1012
1390
  function getCursor(opts) {
1013
1391
  const { row, col, conv } = opts;
1014
1392
  // row: Data, col: string, conv?: (any) => any) {
@@ -1088,52 +1466,79 @@ const edgeFields = [
1088
1466
  ];
1089
1467
  exports.DefaultLimit = 1000;
1090
1468
  // TODO default limit from somewhere
1091
- function defaultEdgeQueryOptions(id1, edgeType) {
1469
+ function defaultEdgeQueryOptions(id1, edgeType, id2) {
1470
+ let cls = clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType));
1471
+ if (id2) {
1472
+ cls = clause.And(cls, clause.Eq("id2", id2));
1473
+ }
1092
1474
  return {
1093
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1475
+ clause: cls,
1094
1476
  orderby: "time DESC",
1095
1477
  limit: exports.DefaultLimit,
1096
1478
  };
1097
1479
  }
1098
- exports.defaultEdgeQueryOptions = defaultEdgeQueryOptions;
1099
1480
  async function loadEdges(options) {
1100
1481
  return loadCustomEdges({ ...options, ctr: AssocEdge });
1101
1482
  }
1102
1483
  exports.loadEdges = loadEdges;
1484
+ function getEdgeClauseAndFields(cls, options) {
1485
+ let fields = edgeFields;
1486
+ if (globalSchema?.transformEdgeRead) {
1487
+ const transformClause = globalSchema.transformEdgeRead();
1488
+ if (!options.disableTransformations) {
1489
+ cls = clause.And(cls, transformClause);
1490
+ }
1491
+ fields = edgeFields.concat(transformClause.columns());
1492
+ }
1493
+ return {
1494
+ cls,
1495
+ fields,
1496
+ };
1497
+ }
1498
+ exports.getEdgeClauseAndFields = getEdgeClauseAndFields;
1103
1499
  async function loadCustomEdges(options) {
1104
- const { id1, edgeType, context } = options;
1500
+ const { cls: actualClause, fields, defaultOptions, tableName, } = await loadEgesInfo(options);
1501
+ const rows = await loadRows({
1502
+ tableName,
1503
+ fields: fields,
1504
+ clause: actualClause,
1505
+ orderby: options.queryOptions?.orderby || defaultOptions.orderby,
1506
+ limit: options.queryOptions?.limit || defaultOptions.limit,
1507
+ context: options.context,
1508
+ });
1509
+ return rows.map((row) => {
1510
+ return new options.ctr(row);
1511
+ });
1512
+ }
1513
+ exports.loadCustomEdges = loadCustomEdges;
1514
+ async function loadEgesInfo(options, id2) {
1515
+ const { id1, edgeType } = options;
1105
1516
  const edgeData = await loadEdgeData(edgeType);
1106
1517
  if (!edgeData) {
1107
1518
  throw new Error(`error loading edge data for ${edgeType}`);
1108
1519
  }
1109
- const defaultOptions = defaultEdgeQueryOptions(id1, edgeType);
1520
+ const defaultOptions = defaultEdgeQueryOptions(id1, edgeType, id2);
1110
1521
  let cls = defaultOptions.clause;
1111
1522
  if (options.queryOptions?.clause) {
1112
1523
  cls = clause.And(cls, options.queryOptions.clause);
1113
1524
  }
1114
- const rows = await loadRows({
1525
+ return {
1526
+ ...getEdgeClauseAndFields(cls, options),
1527
+ defaultOptions,
1115
1528
  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
- });
1529
+ };
1125
1530
  }
1126
- exports.loadCustomEdges = loadCustomEdges;
1127
1531
  async function loadUniqueEdge(options) {
1128
1532
  const { id1, edgeType, context } = options;
1129
1533
  const edgeData = await loadEdgeData(edgeType);
1130
1534
  if (!edgeData) {
1131
1535
  throw new Error(`error loading edge data for ${edgeType}`);
1132
1536
  }
1537
+ const { cls, fields } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
1133
1538
  const row = await loadRow({
1134
1539
  tableName: edgeData.edgeTable,
1135
- fields: edgeFields,
1136
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1540
+ fields: fields,
1541
+ clause: cls,
1137
1542
  context,
1138
1543
  });
1139
1544
  if (!row) {
@@ -1160,21 +1565,28 @@ async function loadRawEdgeCountX(options) {
1160
1565
  if (!edgeData) {
1161
1566
  throw new Error(`error loading edge data for ${edgeType}`);
1162
1567
  }
1568
+ const { cls } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
1163
1569
  const row = await loadRowX({
1164
1570
  tableName: edgeData.edgeTable,
1165
1571
  // sqlite needs as count otherwise it returns count(1)
1166
1572
  fields: ["count(1) as count"],
1167
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1573
+ clause: cls,
1168
1574
  context,
1169
1575
  });
1170
1576
  return parseInt(row["count"], 10) || 0;
1171
1577
  }
1172
1578
  exports.loadRawEdgeCountX = loadRawEdgeCountX;
1173
1579
  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);
1580
+ const { cls: actualClause, fields, tableName, } = await loadEgesInfo(options, options.id2);
1581
+ const row = await loadRow({
1582
+ tableName,
1583
+ fields: fields,
1584
+ clause: actualClause,
1585
+ context: options.context,
1586
+ });
1587
+ if (row) {
1588
+ return new options.ctr(row);
1589
+ }
1178
1590
  }
1179
1591
  exports.loadEdgeForID2 = loadEdgeForID2;
1180
1592
  async function loadNodesByEdge(viewer, id1, edgeType, options) {
@@ -1190,19 +1602,20 @@ async function loadNodesByEdge(viewer, id1, edgeType, options) {
1190
1602
  }
1191
1603
  exports.loadNodesByEdge = loadNodesByEdge;
1192
1604
  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);
1605
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
1606
+ return r instanceof Error ? null : r;
1198
1607
  }
1199
1608
  exports.applyPrivacyPolicyForRow = applyPrivacyPolicyForRow;
1609
+ async function applyPrivacyPolicyForRowImpl(viewer, options, row) {
1610
+ const ent = new options.ent(viewer, row);
1611
+ return applyPrivacyPolicyForEnt(viewer, ent, row, options);
1612
+ }
1200
1613
  async function applyPrivacyPolicyForRowX(viewer, options, row) {
1201
1614
  const ent = new options.ent(viewer, row);
1202
1615
  return await applyPrivacyPolicyForEntX(viewer, ent, row, options);
1203
1616
  }
1204
- exports.applyPrivacyPolicyForRowX = applyPrivacyPolicyForRowX;
1205
- async function applyPrivacyPolicyForRows(viewer, rows, options) {
1617
+ // deprecated. doesn't use entcache
1618
+ async function applyPrivacyPolicyForRowsDeprecated(viewer, rows, options) {
1206
1619
  let m = new Map();
1207
1620
  // apply privacy logic
1208
1621
  await Promise.all(rows.map(async (row) => {
@@ -1213,27 +1626,62 @@ async function applyPrivacyPolicyForRows(viewer, rows, options) {
1213
1626
  }));
1214
1627
  return m;
1215
1628
  }
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];
1629
+ async function applyPrivacyPolicyForRows(viewer, rows, options) {
1630
+ const result = new Array(rows.length);
1631
+ if (!rows.length) {
1632
+ return [];
1633
+ }
1634
+ const entLoader = getEntLoader(viewer, options);
1635
+ await Promise.all(rows.map(async (row, idx) => {
1636
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options, entLoader);
1637
+ if (r instanceof ErrorWrapper) {
1638
+ return;
1639
+ }
1640
+ result[idx] = r;
1641
+ }));
1642
+ // filter ents that aren't visible because of privacy
1643
+ return result.filter((r) => r !== undefined);
1226
1644
  }
1645
+ exports.applyPrivacyPolicyForRows = applyPrivacyPolicyForRows;
1227
1646
  // given a viewer, an id pair, and a map of edgeEnum to EdgeType
1228
1647
  // return the edgeEnum that's set in the group
1229
1648
  async function getEdgeTypeInGroup(viewer, id1, id2, m) {
1230
1649
  let promises = [];
1231
- for (const [k, v] of m) {
1232
- promises.push(loadEdgeWithConst(viewer, id1, id2, k, v));
1650
+ const edgeDatas = await loadEdgeDatas(...Array.from(m.values()));
1651
+ let tableToEdgeEnumMap = new Map();
1652
+ for (const [edgeEnum, edgeType] of m) {
1653
+ const edgeData = edgeDatas.get(edgeType);
1654
+ if (!edgeData) {
1655
+ throw new Error(`could not load edge data for '${edgeType}'`);
1656
+ }
1657
+ const l = tableToEdgeEnumMap.get(edgeData.edgeTable) ?? [];
1658
+ l.push(edgeEnum);
1659
+ tableToEdgeEnumMap.set(edgeData.edgeTable, l);
1233
1660
  }
1661
+ tableToEdgeEnumMap.forEach((edgeEnums, tableName) => {
1662
+ promises.push((async () => {
1663
+ const edgeTypes = edgeEnums.map((edgeEnum) => m.get(edgeEnum));
1664
+ const { cls, fields } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.In("edge_type", edgeTypes), clause.Eq("id2", id2)), {});
1665
+ const rows = await loadRows({
1666
+ tableName,
1667
+ fields,
1668
+ clause: cls,
1669
+ context: viewer.context,
1670
+ });
1671
+ const row = rows[0];
1672
+ if (row) {
1673
+ const edgeType = row.edge_type;
1674
+ for (const [k, v] of m) {
1675
+ if (v === edgeType) {
1676
+ return [k, new AssocEdge(row)];
1677
+ }
1678
+ }
1679
+ }
1680
+ })());
1681
+ });
1234
1682
  const results = await Promise.all(promises);
1235
1683
  for (const res of results) {
1236
- if (res[1]) {
1684
+ if (res && res[1]) {
1237
1685
  return [res[0], res[1]];
1238
1686
  }
1239
1687
  }