@snowtop/ent 0.1.0-alpha1 → 0.1.0-alpha100

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 (172) hide show
  1. package/action/action.d.ts +38 -30
  2. package/action/action.js +22 -7
  3. package/action/executor.d.ts +4 -4
  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 +48 -16
  10. package/action/orchestrator.js +343 -81
  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 +54 -27
  18. package/core/base.js +23 -1
  19. package/core/clause.d.ts +105 -3
  20. package/core/clause.js +563 -30
  21. package/core/config.d.ts +30 -1
  22. package/core/config.js +24 -1
  23. package/core/context.d.ts +5 -3
  24. package/core/context.js +20 -2
  25. package/core/convert.d.ts +1 -1
  26. package/core/date.js +1 -5
  27. package/core/db.d.ts +14 -11
  28. package/core/db.js +22 -8
  29. package/core/ent.d.ts +82 -28
  30. package/core/ent.js +692 -202
  31. package/core/loaders/assoc_count_loader.d.ts +3 -2
  32. package/core/loaders/assoc_count_loader.js +10 -2
  33. package/core/loaders/assoc_edge_loader.d.ts +3 -3
  34. package/core/loaders/assoc_edge_loader.js +13 -15
  35. package/core/loaders/index.d.ts +1 -1
  36. package/core/loaders/index.js +1 -3
  37. package/core/loaders/index_loader.d.ts +2 -2
  38. package/core/loaders/index_loader.js +1 -0
  39. package/core/loaders/loader.js +5 -5
  40. package/core/loaders/object_loader.d.ts +13 -7
  41. package/core/loaders/object_loader.js +95 -32
  42. package/core/loaders/query_loader.d.ts +6 -12
  43. package/core/loaders/query_loader.js +52 -11
  44. package/core/loaders/raw_count_loader.d.ts +2 -2
  45. package/core/loaders/raw_count_loader.js +5 -1
  46. package/core/logger.d.ts +1 -1
  47. package/core/logger.js +1 -0
  48. package/core/privacy.d.ts +26 -25
  49. package/core/privacy.js +23 -24
  50. package/core/query/assoc_query.d.ts +7 -6
  51. package/core/query/assoc_query.js +9 -1
  52. package/core/query/custom_clause_query.d.ts +26 -0
  53. package/core/query/custom_clause_query.js +78 -0
  54. package/core/query/custom_query.d.ts +20 -5
  55. package/core/query/custom_query.js +87 -12
  56. package/core/query/index.d.ts +1 -0
  57. package/core/query/index.js +3 -1
  58. package/core/query/query.d.ts +8 -4
  59. package/core/query/query.js +101 -53
  60. package/core/query/shared_assoc_test.d.ts +2 -1
  61. package/core/query/shared_assoc_test.js +35 -45
  62. package/core/query/shared_test.d.ts +8 -1
  63. package/core/query/shared_test.js +469 -236
  64. package/core/viewer.d.ts +4 -3
  65. package/core/viewer.js +5 -1
  66. package/graphql/builtins/connection.js +3 -3
  67. package/graphql/builtins/edge.js +2 -2
  68. package/graphql/builtins/node.js +1 -1
  69. package/graphql/graphql.d.ts +17 -9
  70. package/graphql/graphql.js +47 -30
  71. package/graphql/index.d.ts +1 -1
  72. package/graphql/index.js +3 -4
  73. package/graphql/mutations/union.d.ts +2 -0
  74. package/graphql/mutations/union.js +35 -0
  75. package/graphql/node_resolver.d.ts +0 -1
  76. package/graphql/query/connection_type.d.ts +9 -9
  77. package/graphql/query/connection_type.js +6 -6
  78. package/graphql/query/edge_connection.d.ts +9 -9
  79. package/graphql/query/page_info.d.ts +1 -1
  80. package/graphql/query/page_info.js +4 -4
  81. package/graphql/query/shared_assoc_test.js +3 -3
  82. package/graphql/query/shared_edge_connection.js +1 -19
  83. package/graphql/scalars/time.d.ts +1 -1
  84. package/imports/index.d.ts +6 -1
  85. package/imports/index.js +19 -4
  86. package/index.d.ts +23 -1
  87. package/index.js +32 -6
  88. package/package.json +18 -17
  89. package/parse_schema/parse.d.ts +45 -8
  90. package/parse_schema/parse.js +193 -15
  91. package/schema/base_schema.d.ts +38 -1
  92. package/schema/base_schema.js +53 -2
  93. package/schema/field.d.ts +75 -21
  94. package/schema/field.js +185 -72
  95. package/schema/index.d.ts +4 -2
  96. package/schema/index.js +15 -2
  97. package/schema/json_field.d.ts +13 -1
  98. package/schema/json_field.js +28 -1
  99. package/schema/schema.d.ts +125 -10
  100. package/schema/schema.js +133 -5
  101. package/schema/struct_field.d.ts +27 -0
  102. package/schema/struct_field.js +138 -0
  103. package/schema/union_field.d.ts +23 -0
  104. package/schema/union_field.js +79 -0
  105. package/scripts/custom_compiler.js +10 -6
  106. package/scripts/custom_graphql.js +224 -36
  107. package/scripts/{transform_schema.d.ts → migrate_v0.1.d.ts} +0 -0
  108. package/scripts/migrate_v0.1.js +36 -0
  109. package/scripts/move_types.d.ts +1 -0
  110. package/scripts/move_types.js +117 -0
  111. package/scripts/read_schema.js +35 -6
  112. package/testutils/action/complex_schemas.d.ts +69 -0
  113. package/testutils/action/complex_schemas.js +398 -0
  114. package/testutils/builder.d.ts +52 -49
  115. package/testutils/builder.js +143 -44
  116. package/testutils/context/test_context.d.ts +2 -2
  117. package/testutils/context/test_context.js +7 -1
  118. package/testutils/db/fixture.d.ts +10 -0
  119. package/testutils/db/fixture.js +26 -0
  120. package/testutils/db/{test_db.d.ts → temp_db.d.ts} +26 -9
  121. package/testutils/db/{test_db.js → temp_db.js} +190 -46
  122. package/testutils/db/value.d.ts +7 -0
  123. package/testutils/db/value.js +251 -0
  124. package/testutils/db_mock.d.ts +16 -4
  125. package/testutils/db_mock.js +51 -6
  126. package/testutils/db_time_zone.d.ts +4 -0
  127. package/testutils/db_time_zone.js +41 -0
  128. package/testutils/ent-graphql-tests/index.d.ts +9 -1
  129. package/testutils/ent-graphql-tests/index.js +53 -25
  130. package/testutils/fake_data/const.d.ts +2 -1
  131. package/testutils/fake_data/const.js +3 -0
  132. package/testutils/fake_data/fake_contact.d.ts +10 -10
  133. package/testutils/fake_data/fake_contact.js +23 -21
  134. package/testutils/fake_data/fake_event.d.ts +8 -9
  135. package/testutils/fake_data/fake_event.js +25 -28
  136. package/testutils/fake_data/fake_tag.d.ts +36 -0
  137. package/testutils/fake_data/fake_tag.js +89 -0
  138. package/testutils/fake_data/fake_user.d.ts +10 -11
  139. package/testutils/fake_data/fake_user.js +20 -23
  140. package/testutils/fake_data/index.js +5 -1
  141. package/testutils/fake_data/internal.d.ts +2 -0
  142. package/testutils/fake_data/internal.js +7 -1
  143. package/testutils/fake_data/tag_query.d.ts +13 -0
  144. package/testutils/fake_data/tag_query.js +43 -0
  145. package/testutils/fake_data/test_helpers.d.ts +11 -4
  146. package/testutils/fake_data/test_helpers.js +29 -13
  147. package/testutils/fake_data/user_query.d.ts +13 -6
  148. package/testutils/fake_data/user_query.js +54 -22
  149. package/testutils/fake_log.d.ts +3 -3
  150. package/testutils/parse_sql.d.ts +6 -0
  151. package/testutils/parse_sql.js +16 -2
  152. package/testutils/test_edge_global_schema.d.ts +15 -0
  153. package/testutils/test_edge_global_schema.js +62 -0
  154. package/testutils/write.d.ts +2 -2
  155. package/testutils/write.js +33 -7
  156. package/tsc/ast.d.ts +44 -0
  157. package/tsc/ast.js +277 -0
  158. package/tsc/compilerOptions.d.ts +6 -0
  159. package/tsc/compilerOptions.js +45 -2
  160. package/tsc/move_generated.d.ts +1 -0
  161. package/tsc/move_generated.js +164 -0
  162. package/tsc/transform.d.ts +22 -0
  163. package/tsc/transform.js +181 -0
  164. package/tsc/transform_action.d.ts +22 -0
  165. package/tsc/transform_action.js +183 -0
  166. package/tsc/transform_ent.d.ts +17 -0
  167. package/tsc/transform_ent.js +59 -0
  168. package/tsc/transform_schema.d.ts +27 -0
  169. package/tsc/transform_schema.js +383 -0
  170. package/graphql/enums.d.ts +0 -3
  171. package/graphql/enums.js +0 -25
  172. package/scripts/transform_schema.js +0 -288
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,14 +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.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.applyPrivacyPolicyForEntX = exports.applyPrivacyPolicyForEnt = 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;
26
- exports.getEdgeTypeInGroup = 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;
27
31
  const db_1 = __importStar(require("./db"));
28
32
  const privacy_1 = require("./privacy");
29
33
  const clause = __importStar(require("./clause"));
30
34
  const action_1 = require("../action");
31
35
  const logger_1 = require("./logger");
32
36
  const dataloader_1 = __importDefault(require("dataloader"));
37
+ const schema_1 = require("../schema/");
33
38
  // TODO kill this and createDataLoader
34
39
  class cacheMap {
35
40
  constructor(options) {
@@ -39,7 +44,7 @@ class cacheMap {
39
44
  get(key) {
40
45
  const ret = this.m.get(key);
41
46
  if (ret) {
42
- (0, logger_1.log)("query", {
47
+ (0, logger_1.log)("cache", {
43
48
  "dataloader-cache-hit": key,
44
49
  "tableName": this.options.tableName,
45
50
  });
@@ -56,12 +61,41 @@ class cacheMap {
56
61
  return this.m.clear();
57
62
  }
58
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
+ }
59
92
  function createDataLoader(options) {
60
93
  const loaderOptions = {};
61
94
  // if query logging is enabled, we should log what's happening with loader
62
95
  if ((0, logger_1.logEnabled)("query")) {
63
96
  loaderOptions.cacheMap = new cacheMap(options);
64
97
  }
98
+ // something here brokwn with strict:true
65
99
  return new dataloader_1.default(async (ids) => {
66
100
  if (!ids.length) {
67
101
  return [];
@@ -85,28 +119,151 @@ function createDataLoader(options) {
85
119
  return result;
86
120
  }, loaderOptions);
87
121
  }
88
- // 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;
89
208
  async function loadEnt(viewer, id, options) {
90
- const row = await options.loaderFactory.createLoader(viewer.context).load(id);
91
- 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;
92
216
  }
93
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
+ }
94
243
  // this is the same implementation-wise (right now) as loadEnt. it's just clearer that it's not loaded via ID.
95
244
  // used for load via email address etc
96
245
  async function loadEntViaKey(viewer, key, options) {
97
246
  const row = await options.loaderFactory
98
247
  .createLoader(viewer.context)
99
248
  .load(key);
100
- 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;
101
254
  }
102
255
  exports.loadEntViaKey = loadEntViaKey;
103
256
  async function loadEntX(viewer, id, options) {
104
- const row = await options.loaderFactory.createLoader(viewer.context).load(id);
105
- if (!row) {
106
- // todo make this better
107
- 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`);
108
261
  }
109
- 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;
110
267
  }
111
268
  exports.loadEntX = loadEntX;
112
269
  async function loadEntXViaKey(viewer, key, options) {
@@ -117,9 +274,16 @@ async function loadEntXViaKey(viewer, key, options) {
117
274
  // todo make this better
118
275
  throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${key}`);
119
276
  }
120
- 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;
121
282
  }
122
283
  exports.loadEntXViaKey = loadEntXViaKey;
284
+ /**
285
+ * @deprecated use loadCustomEnts
286
+ */
123
287
  async function loadEntFromClause(viewer, options, clause) {
124
288
  const rowOptions = {
125
289
  ...options,
@@ -127,12 +291,18 @@ async function loadEntFromClause(viewer, options, clause) {
127
291
  context: viewer.context,
128
292
  };
129
293
  const row = await loadRow(rowOptions);
130
- return await applyPrivacyPolicyForRow(viewer, options, row);
294
+ if (row === null) {
295
+ return null;
296
+ }
297
+ return applyPrivacyPolicyForRow(viewer, options, row);
131
298
  }
132
299
  exports.loadEntFromClause = loadEntFromClause;
133
300
  // same as loadEntFromClause
134
301
  // only works for ents where primary key is "id"
135
302
  // use loadEnt with a loaderFactory if different
303
+ /**
304
+ * @deprecated use loadCustomEnts
305
+ */
136
306
  async function loadEntXFromClause(viewer, options, clause) {
137
307
  const rowOptions = {
138
308
  ...options,
@@ -140,45 +310,26 @@ async function loadEntXFromClause(viewer, options, clause) {
140
310
  context: viewer.context,
141
311
  };
142
312
  const row = await loadRowX(rowOptions);
143
- const ent = new options.ent(viewer, row);
144
- return await applyPrivacyPolicyForEntX(viewer, ent);
313
+ return await applyPrivacyPolicyForRowX(viewer, options, row);
145
314
  }
146
315
  exports.loadEntXFromClause = loadEntXFromClause;
147
316
  async function loadEnts(viewer, options, ...ids) {
148
317
  if (!ids.length) {
149
318
  return new Map();
150
319
  }
151
- let loaded = false;
152
- let rows = [];
153
- // TODO loadMany everywhere
154
- const l = options.loaderFactory.createLoader(viewer.context);
155
- if (l.loadMany) {
156
- loaded = true;
157
- rows = await l.loadMany(ids);
158
- }
159
- // TODO rewrite all of this
320
+ // result
160
321
  let m = new Map();
161
- if (loaded) {
162
- let rows2 = [];
163
- for (const row of rows) {
164
- if (!row) {
165
- continue;
166
- }
167
- if (row instanceof Error) {
168
- throw row;
169
- }
170
- 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;
171
326
  }
172
- m = await applyPrivacyPolicyForRows(viewer, rows2, options);
173
- }
174
- else {
175
- m = await loadEntsFromClause(viewer,
176
- // this is always "id" if not using an ObjectLoaderFactory
177
- clause.In("id", ...ids), options);
327
+ if (r instanceof ErrorWrapper) {
328
+ continue;
329
+ }
330
+ m.set(r.id, r);
178
331
  }
179
332
  return m;
180
- // TODO do we want to change this to be a map not a list so that it's easy to check for existence?
181
- // TODO eventually this should be doing a cache then db queyr and maybe depend on dataloader to get all the results at once
182
333
  }
183
334
  exports.loadEnts = loadEnts;
184
335
  // calls loadEnts and returns the results sorted in the order they were passed in
@@ -197,6 +348,9 @@ async function loadEntsList(viewer, options, ...ids) {
197
348
  exports.loadEntsList = loadEntsList;
198
349
  // we return a map here so that any sorting for queries that exist
199
350
  // can be done in O(N) time
351
+ /**
352
+ * @deperecated use loadCustomEnts
353
+ */
200
354
  async function loadEntsFromClause(viewer, clause, options) {
201
355
  const rowOptions = {
202
356
  ...options,
@@ -204,84 +358,192 @@ async function loadEntsFromClause(viewer, clause, options) {
204
358
  context: viewer.context,
205
359
  };
206
360
  const rows = await loadRows(rowOptions);
207
- return await applyPrivacyPolicyForRows(viewer, rows, options);
361
+ return applyPrivacyPolicyForRowsDeprecated(viewer, rows, options);
208
362
  }
209
363
  exports.loadEntsFromClause = loadEntsFromClause;
210
364
  async function loadCustomEnts(viewer, options, query) {
211
365
  const rows = await loadCustomData(options, query, viewer.context);
212
- const result = new Array(rows.length);
213
- await Promise.all(rows.map(async (row, idx) => {
214
- const ent = new options.ent(viewer, row);
215
- let privacyEnt = await applyPrivacyPolicyForEnt(viewer, ent);
216
- if (privacyEnt) {
217
- result[idx] = privacyEnt;
218
- }
219
- }));
220
- // filter ents that aren't visible because of privacy
221
- return result.filter((r) => r !== undefined);
366
+ return applyPrivacyPolicyForRows(viewer, rows, options);
222
367
  }
223
368
  exports.loadCustomEnts = loadCustomEnts;
224
369
  function isClause(opts) {
225
370
  const cls = opts;
226
371
  return cls.clause !== undefined && cls.values !== undefined;
227
372
  }
228
- function isRawQuery(opts) {
373
+ function isParameterizedQuery(opts) {
229
374
  return opts.query !== undefined;
230
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
+ */
231
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
+ return clause.And(cls, optClause);
441
+ }
232
442
  if (typeof query === "string") {
233
443
  // no caching, perform raw query
234
- return await performRawQuery(query, [], []);
444
+ return performRawQuery(query, [], []);
235
445
  }
236
446
  else if (isClause(query)) {
447
+ // if a Clause is passed in and we have a default clause
448
+ // associated with the query, pass that in
449
+ // if we want to disableTransformations, need to indicate that with
450
+ // disableTransformations option
237
451
  // this will have rudimentary caching but nothing crazy
238
- return await loadRows({
452
+ return loadRows({
239
453
  ...options,
240
- clause: query,
454
+ clause: getClause(query),
241
455
  context: context,
242
456
  });
243
457
  }
244
- else if (isRawQuery(query)) {
458
+ else if (isParameterizedQuery(query)) {
245
459
  // no caching, perform raw query
246
- return await performRawQuery(query.query, query.values || [], query.logValues);
460
+ return performRawQuery(query.query, query.values || [], query.logValues);
247
461
  }
248
462
  else {
463
+ let cls = query.clause;
464
+ if (!query.disableTransformations) {
465
+ cls = getClause(cls);
466
+ }
249
467
  // this will have rudimentary caching but nothing crazy
250
- return await loadRows({
468
+ return loadRows({
251
469
  ...query,
252
470
  ...options,
253
471
  context: context,
472
+ clause: cls,
254
473
  });
255
474
  }
256
475
  }
257
- exports.loadCustomData = loadCustomData;
258
476
  // Derived ents
477
+ // no ent caching
259
478
  async function loadDerivedEnt(viewer, data, loader) {
260
479
  const ent = new loader(viewer, data);
261
- return await applyPrivacyPolicyForEnt(viewer, ent);
480
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, {
481
+ ent: loader,
482
+ });
483
+ if (r instanceof Error) {
484
+ return null;
485
+ }
486
+ return r;
262
487
  }
263
488
  exports.loadDerivedEnt = loadDerivedEnt;
489
+ // won't have caching yet either
264
490
  async function loadDerivedEntX(viewer, data, loader) {
265
491
  const ent = new loader(viewer, data);
266
- return await applyPrivacyPolicyForEntX(viewer, ent);
492
+ return await applyPrivacyPolicyForEntX(viewer, ent, data, { ent: loader });
267
493
  }
268
494
  exports.loadDerivedEntX = loadDerivedEntX;
269
- async function applyPrivacyPolicyForEnt(viewer, ent) {
270
- if (ent) {
271
- const visible = await (0, privacy_1.applyPrivacyPolicy)(viewer, ent.privacyPolicy, ent);
272
- if (visible) {
273
- return ent;
274
- }
495
+ // everything calls into this two so should be fine
496
+ // TODO is there a smarter way to not instantiate two objects here?
497
+ async function applyPrivacyPolicyForEnt(viewer, ent, data, fieldPrivacyOptions) {
498
+ const error = await (0, privacy_1.applyPrivacyPolicyImpl)(viewer, ent.getPrivacyPolicy(), ent);
499
+ if (error === null) {
500
+ return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
501
+ }
502
+ return error;
503
+ }
504
+ async function applyPrivacyPolicyForEntX(viewer, ent, data, options) {
505
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, options);
506
+ if (r instanceof Error) {
507
+ throw r;
275
508
  }
276
- return null;
509
+ if (r === null) {
510
+ throw new Error(`couldn't apply privacyPoliy for ent ${ent.id}`);
511
+ }
512
+ return r;
277
513
  }
278
- exports.applyPrivacyPolicyForEnt = applyPrivacyPolicyForEnt;
279
- async function applyPrivacyPolicyForEntX(viewer, ent) {
280
- // this will throw
281
- await (0, privacy_1.applyPrivacyPolicyX)(viewer, ent.privacyPolicy, ent);
514
+ async function doFieldPrivacy(viewer, ent, data, options) {
515
+ if (!options.fieldPrivacy) {
516
+ return ent;
517
+ }
518
+ const promises = [];
519
+ let somethingChanged = false;
520
+ const origData = {
521
+ ...data,
522
+ };
523
+ for (const [k, policy] of options.fieldPrivacy) {
524
+ const curr = data[k];
525
+ if (curr === null || curr === undefined) {
526
+ continue;
527
+ }
528
+ promises.push((async () => {
529
+ // don't do anything if key is null or for some reason missing
530
+ const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
531
+ if (!r) {
532
+ data[k] = null;
533
+ somethingChanged = true;
534
+ }
535
+ })());
536
+ }
537
+ await Promise.all(promises);
538
+ if (somethingChanged) {
539
+ // have to create new instance
540
+ const ent = new options.ent(viewer, data);
541
+ ent.__setRawDBData(origData);
542
+ return ent;
543
+ }
544
+ ent.__setRawDBData(origData);
282
545
  return ent;
283
546
  }
284
- exports.applyPrivacyPolicyForEntX = applyPrivacyPolicyForEntX;
285
547
  function logQuery(query, logValues) {
286
548
  (0, logger_1.log)("query", {
287
549
  query: query,
@@ -289,6 +551,7 @@ function logQuery(query, logValues) {
289
551
  });
290
552
  (0, logger_1.logTrace)();
291
553
  }
554
+ exports.logQuery = logQuery;
292
555
  // TODO long term figure out if this API should be exposed
293
556
  async function loadRowX(options) {
294
557
  const result = await loadRow(options);
@@ -311,27 +574,26 @@ async function loadRow(options) {
311
574
  }
312
575
  const query = buildQuery(options);
313
576
  logQuery(query, options.clause.logValues());
314
- try {
315
- const pool = db_1.default.getInstance().getPool();
316
- const res = await pool.query(query, options.clause.values());
317
- if (res.rowCount != 1) {
318
- if (res.rowCount > 1) {
319
- (0, logger_1.log)("error", "got more than one row for query " + query);
320
- }
321
- return null;
322
- }
323
- // put the row in the cache...
324
- if (cache) {
325
- cache.primeCache(options, res.rows[0]);
577
+ const pool = db_1.default.getInstance().getPool();
578
+ const res = await pool.query(query, options.clause.values());
579
+ if (res.rowCount != 1) {
580
+ if (res.rowCount > 1) {
581
+ (0, logger_1.log)("error", "got more than one row for query " + query);
326
582
  }
327
- return res.rows[0];
328
- }
329
- catch (e) {
330
- (0, logger_1.log)("error", e);
331
583
  return null;
332
584
  }
585
+ // put the row in the cache...
586
+ if (cache) {
587
+ cache.primeCache(options, res.rows[0]);
588
+ }
589
+ return res.rows[0];
333
590
  }
334
591
  exports.loadRow = loadRow;
592
+ var _logQueryWithError = false;
593
+ function ___setLogQueryErrorWithError(val) {
594
+ _logQueryWithError = val || false;
595
+ }
596
+ exports.___setLogQueryErrorWithError = ___setLogQueryErrorWithError;
335
597
  // this always goes to the db, no cache, nothing
336
598
  async function performRawQuery(query, values, logValues) {
337
599
  const pool = db_1.default.getInstance().getPool();
@@ -341,9 +603,11 @@ async function performRawQuery(query, values, logValues) {
341
603
  return res.rows;
342
604
  }
343
605
  catch (e) {
344
- // TODO need to change every query to catch an error!
345
- (0, logger_1.log)("error", e);
346
- return [];
606
+ if (_logQueryWithError) {
607
+ const msg = e.message;
608
+ throw new Error(`error \`${msg}\` running query: \`${query}\``);
609
+ }
610
+ throw e;
347
611
  }
348
612
  }
349
613
  exports.performRawQuery = performRawQuery;
@@ -402,10 +666,41 @@ function buildGroupQuery(options) {
402
666
  ];
403
667
  }
404
668
  exports.buildGroupQuery = buildGroupQuery;
669
+ class RawQueryOperation {
670
+ constructor(queries) {
671
+ this.queries = queries;
672
+ }
673
+ async performWrite(queryer, context) {
674
+ for (const q of this.queries) {
675
+ if (typeof q === "string") {
676
+ logQuery(q, []);
677
+ await queryer.query(q);
678
+ }
679
+ else {
680
+ logQuery(q.query, q.logValues || []);
681
+ await queryer.query(q.query, q.values);
682
+ }
683
+ }
684
+ }
685
+ performWriteSync(queryer, context) {
686
+ for (const q of this.queries) {
687
+ if (typeof q === "string") {
688
+ logQuery(q, []);
689
+ queryer.execSync(q);
690
+ }
691
+ else {
692
+ logQuery(q.query, q.logValues || []);
693
+ queryer.execSync(q.query, q.values);
694
+ }
695
+ }
696
+ }
697
+ }
698
+ exports.RawQueryOperation = RawQueryOperation;
405
699
  class EditNodeOperation {
406
700
  constructor(options, existingEnt = null) {
407
701
  this.options = options;
408
702
  this.existingEnt = existingEnt;
703
+ this.row = null;
409
704
  this.placeholderID = options.placeholderID;
410
705
  }
411
706
  resolve(executor) {
@@ -439,9 +734,12 @@ class EditNodeOperation {
439
734
  };
440
735
  if (this.existingEnt) {
441
736
  if (this.hasData(options.fields)) {
442
- this.row = await editRow(queryer, options, this.existingEnt.id, "RETURNING *");
737
+ // even this with returning * may not always work if transformed...
738
+ // we can have a transformed flag to see if it should be returned?
739
+ this.row = await editRow(queryer, options, "RETURNING *");
443
740
  }
444
741
  else {
742
+ // @ts-ignore
445
743
  this.row = this.existingEnt["data"];
446
744
  }
447
745
  }
@@ -450,20 +748,36 @@ class EditNodeOperation {
450
748
  }
451
749
  }
452
750
  reloadRow(queryer, id, options) {
751
+ // TODO this isn't always an ObjectLoader. should throw or figure out a way to get query
752
+ // and run this on its own...
753
+ const loader = this.options.loadEntOptions.loaderFactory.createLoader(options.context);
754
+ const opts = loader.getOptions();
755
+ let cls = clause.Eq(options.key, id);
756
+ if (opts.clause) {
757
+ let optionClause;
758
+ if (typeof opts.clause === "function") {
759
+ optionClause = opts.clause();
760
+ }
761
+ else {
762
+ optionClause = opts.clause;
763
+ }
764
+ if (optionClause) {
765
+ cls = clause.And(cls, optionClause);
766
+ }
767
+ }
453
768
  const query = buildQuery({
454
- fields: ["*"],
769
+ fields: opts.fields.length ? opts.fields : ["*"],
455
770
  tableName: options.tableName,
456
- clause: clause.Eq(options.key, id),
771
+ clause: cls,
457
772
  });
458
773
  // special case log here because we're not going through any of the normal
459
774
  // methods here because those are async and this is sync
460
775
  // this is the only place we're doing this so only handling here
461
776
  logQuery(query, [id]);
462
777
  const r = queryer.querySync(query, [id]);
463
- if (r.rows.length !== 1) {
464
- throw new Error(`couldn't reload row for ${id}`);
778
+ if (r.rows.length === 1) {
779
+ this.row = r.rows[0];
465
780
  }
466
- this.row = r.rows[0];
467
781
  }
468
782
  performWriteSync(queryer, context) {
469
783
  let options = {
@@ -472,10 +786,11 @@ class EditNodeOperation {
472
786
  };
473
787
  if (this.existingEnt) {
474
788
  if (this.hasData(this.options.fields)) {
475
- editRowSync(queryer, options, this.existingEnt.id, "RETURNING *");
789
+ editRowSync(queryer, options, "RETURNING *");
476
790
  this.reloadRow(queryer, this.existingEnt.id, options);
477
791
  }
478
792
  else {
793
+ // @ts-ignore
479
794
  this.row = this.existingEnt["data"];
480
795
  }
481
796
  }
@@ -492,12 +807,27 @@ class EditNodeOperation {
492
807
  if (!this.row) {
493
808
  return null;
494
809
  }
495
- return new this.options.ent(viewer, this.row);
810
+ return new this.options.loadEntOptions.ent(viewer, this.row);
496
811
  }
497
812
  }
498
813
  exports.EditNodeOperation = EditNodeOperation;
814
+ let globalSchema;
815
+ function setGlobalSchema(val) {
816
+ globalSchema = val;
817
+ }
818
+ exports.setGlobalSchema = setGlobalSchema;
819
+ function clearGlobalSchema() {
820
+ globalSchema = undefined;
821
+ }
822
+ exports.clearGlobalSchema = clearGlobalSchema;
823
+ // used by tests. no guarantee will always exist
824
+ function __hasGlobalSchema() {
825
+ return globalSchema !== undefined;
826
+ }
827
+ exports.__hasGlobalSchema = __hasGlobalSchema;
499
828
  class EdgeOperation {
500
- constructor(edgeInput, options) {
829
+ constructor(builder, edgeInput, options) {
830
+ this.builder = builder;
501
831
  this.edgeInput = edgeInput;
502
832
  this.options = options;
503
833
  }
@@ -533,7 +863,31 @@ class EdgeOperation {
533
863
  }
534
864
  }
535
865
  getDeleteRowParams(edgeData, edge, context) {
866
+ let transformed = null;
867
+ let op = schema_1.SQLStatementOperation.Delete;
868
+ let updateData = null;
869
+ // TODO respect disableTransformations
870
+ if (globalSchema?.transformEdgeWrite) {
871
+ transformed = globalSchema.transformEdgeWrite({
872
+ op: schema_1.SQLStatementOperation.Delete,
873
+ edge,
874
+ });
875
+ if (transformed) {
876
+ op = transformed.op;
877
+ if (transformed.op === schema_1.SQLStatementOperation.Insert) {
878
+ throw new Error(`cannot currently transform a delete into an insert`);
879
+ }
880
+ if (transformed.op === schema_1.SQLStatementOperation.Update) {
881
+ if (!transformed.data) {
882
+ throw new Error(`cannot transform a delete into an update without providing data`);
883
+ }
884
+ updateData = transformed.data;
885
+ }
886
+ }
887
+ }
536
888
  return {
889
+ op,
890
+ updateData,
537
891
  options: {
538
892
  tableName: edgeData.edgeTable,
539
893
  context,
@@ -543,11 +897,36 @@ class EdgeOperation {
543
897
  }
544
898
  async performDeleteWrite(q, edgeData, edge, context) {
545
899
  const params = this.getDeleteRowParams(edgeData, edge, context);
546
- return deleteRows(q, params.options, params.clause);
900
+ if (params.op === schema_1.SQLStatementOperation.Delete) {
901
+ return deleteRows(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
+ await editRow(q, {
908
+ tableName: params.options.tableName,
909
+ whereClause: params.clause,
910
+ fields: params.updateData,
911
+ fieldsToLog: params.updateData,
912
+ });
913
+ }
547
914
  }
548
915
  performDeleteWriteSync(q, edgeData, edge, context) {
549
916
  const params = this.getDeleteRowParams(edgeData, edge, context);
550
- return deleteRowsSync(q, params.options, params.clause);
917
+ if (params.op === schema_1.SQLStatementOperation.Delete) {
918
+ return deleteRowsSync(q, params.options, params.clause);
919
+ }
920
+ else {
921
+ if (params.op !== schema_1.SQLStatementOperation.Update) {
922
+ throw new Error(`invalid operation ${params.op}`);
923
+ }
924
+ editRowSync(q, {
925
+ tableName: params.options.tableName,
926
+ whereClause: params.clause,
927
+ fields: params.updateData,
928
+ });
929
+ }
551
930
  }
552
931
  getInsertRowParams(edgeData, edge, context) {
553
932
  const fields = {
@@ -566,6 +945,30 @@ class EdgeOperation {
566
945
  // maybe when actions exist?
567
946
  fields["time"] = new Date().toISOString();
568
947
  }
948
+ const onConflictFields = ["data"];
949
+ if (globalSchema?.extraEdgeFields) {
950
+ for (const name in globalSchema.extraEdgeFields) {
951
+ const f = globalSchema.extraEdgeFields[name];
952
+ if (f.defaultValueOnCreate) {
953
+ const storageKey = (0, schema_1.getStorageKey)(f, name);
954
+ fields[storageKey] = f.defaultValueOnCreate(this.builder, {});
955
+ // onconflict make sure we override the default values
956
+ // e.g. setting deleted_at = null for soft delete
957
+ onConflictFields.push(storageKey);
958
+ }
959
+ }
960
+ }
961
+ // TODO respect disableTransformations
962
+ let transformed = null;
963
+ if (globalSchema?.transformEdgeWrite) {
964
+ transformed = globalSchema.transformEdgeWrite({
965
+ op: schema_1.SQLStatementOperation.Insert,
966
+ edge,
967
+ });
968
+ if (transformed) {
969
+ throw new Error(`transforming an insert edge not currently supported`);
970
+ }
971
+ }
569
972
  return [
570
973
  {
571
974
  tableName: edgeData.edgeTable,
@@ -573,7 +976,9 @@ class EdgeOperation {
573
976
  fieldsToLog: fields,
574
977
  context,
575
978
  },
576
- "ON CONFLICT(id1, edge_type, id2) DO UPDATE SET data = EXCLUDED.data",
979
+ `ON CONFLICT(id1, edge_type, id2) DO UPDATE SET ${onConflictFields
980
+ .map((f) => `${f} = EXCLUDED.${f}`)
981
+ .join(", ")}`,
577
982
  ];
578
983
  }
579
984
  async performInsertWrite(q, edgeData, edge, context) {
@@ -610,7 +1015,7 @@ class EdgeOperation {
610
1015
  }
611
1016
  }
612
1017
  symmetricEdge() {
613
- return new EdgeOperation({
1018
+ return new EdgeOperation(this.builder, {
614
1019
  id1: this.edgeInput.id2,
615
1020
  id1Type: this.edgeInput.id2Type,
616
1021
  id2: this.edgeInput.id1,
@@ -626,7 +1031,7 @@ class EdgeOperation {
626
1031
  });
627
1032
  }
628
1033
  inverseEdge(edgeData) {
629
- return new EdgeOperation({
1034
+ return new EdgeOperation(this.builder, {
630
1035
  id1: this.edgeInput.id2,
631
1036
  id1Type: this.edgeInput.id2Type,
632
1037
  id2: this.edgeInput.id1,
@@ -694,7 +1099,7 @@ class EdgeOperation {
694
1099
  if (data) {
695
1100
  edge.data = data;
696
1101
  }
697
- return new EdgeOperation(edge, {
1102
+ return new EdgeOperation(builder, edge, {
698
1103
  operation: action_1.WriteOperation.Insert,
699
1104
  id2Placeholder,
700
1105
  id1Placeholder,
@@ -715,7 +1120,7 @@ class EdgeOperation {
715
1120
  if (data) {
716
1121
  edge.data = data;
717
1122
  }
718
- return new EdgeOperation(edge, {
1123
+ return new EdgeOperation(builder, edge, {
719
1124
  operation: action_1.WriteOperation.Insert,
720
1125
  id1Placeholder,
721
1126
  id2Placeholder,
@@ -733,7 +1138,7 @@ class EdgeOperation {
733
1138
  id2Type: "",
734
1139
  id1Type: "",
735
1140
  };
736
- return new EdgeOperation(edge, {
1141
+ return new EdgeOperation(builder, edge, {
737
1142
  operation: action_1.WriteOperation.Delete,
738
1143
  });
739
1144
  }
@@ -748,7 +1153,7 @@ class EdgeOperation {
748
1153
  id2Type: "",
749
1154
  id1Type: "",
750
1155
  };
751
- return new EdgeOperation(edge, {
1156
+ return new EdgeOperation(builder, edge, {
752
1157
  operation: action_1.WriteOperation.Delete,
753
1158
  });
754
1159
  }
@@ -760,24 +1165,26 @@ function isSyncQueryer(queryer) {
760
1165
  async function mutateRow(queryer, query, values, logValues, options) {
761
1166
  logQuery(query, logValues);
762
1167
  let cache = options.context?.cache;
1168
+ let res;
763
1169
  try {
764
- let res;
765
1170
  if (isSyncQueryer(queryer)) {
766
1171
  res = queryer.execSync(query, values);
767
1172
  }
768
1173
  else {
769
1174
  res = await queryer.exec(query, values);
770
1175
  }
771
- if (cache) {
772
- cache.clearCache();
1176
+ }
1177
+ catch (e) {
1178
+ if (_logQueryWithError) {
1179
+ const msg = e.message;
1180
+ throw new Error(`error \`${msg}\` running query: \`${query}\``);
773
1181
  }
774
- return res;
1182
+ throw e;
775
1183
  }
776
- catch (err) {
777
- // TODO:::why is this not rethrowing?
778
- (0, logger_1.log)("error", err);
779
- throw err;
1184
+ if (cache) {
1185
+ cache.clearCache();
780
1186
  }
1187
+ return res;
781
1188
  }
782
1189
  function mutateRowSync(queryer, query, values, logValues, options) {
783
1190
  logQuery(query, logValues);
@@ -789,10 +1196,12 @@ function mutateRowSync(queryer, query, values, logValues, options) {
789
1196
  }
790
1197
  return res;
791
1198
  }
792
- catch (err) {
793
- // TODO:::why is this not rethrowing?
794
- (0, logger_1.log)("error", err);
795
- throw err;
1199
+ catch (e) {
1200
+ if (_logQueryWithError) {
1201
+ const msg = e.message;
1202
+ throw new Error(`error \`${msg}\` running query: \`${query}\``);
1203
+ }
1204
+ throw e;
796
1205
  }
797
1206
  }
798
1207
  function buildInsertQuery(options, suffix) {
@@ -845,32 +1254,47 @@ function createRowSync(queryer, options, suffix) {
845
1254
  return null;
846
1255
  }
847
1256
  exports.createRowSync = createRowSync;
848
- function buildUpdateQuery(options, id, suffix) {
1257
+ function buildUpdateQuery(options, suffix) {
849
1258
  let valsString = [];
850
1259
  let values = [];
851
1260
  let logValues = [];
852
1261
  const dialect = db_1.default.getDialect();
853
1262
  let idx = 1;
854
1263
  for (const key in options.fields) {
855
- values.push(options.fields[key]);
856
- if (options.fieldsToLog) {
857
- logValues.push(options.fieldsToLog[key]);
858
- }
859
- if (dialect === db_1.Dialect.Postgres) {
860
- valsString.push(`${key} = $${idx}`);
861
- idx++;
1264
+ if (options.expressions && options.expressions.has(key)) {
1265
+ const cls = options.expressions.get(key);
1266
+ valsString.push(`${key} = ${cls.clause(idx)}`);
1267
+ // TODO need to test a clause with more than one value...
1268
+ const newVals = cls.values();
1269
+ idx += newVals.length;
1270
+ values.push(...newVals);
1271
+ logValues.push(...cls.logValues());
862
1272
  }
863
1273
  else {
864
- valsString.push(`${key} = ?`);
1274
+ const val = options.fields[key];
1275
+ values.push(val);
1276
+ if (options.fieldsToLog) {
1277
+ logValues.push(options.fieldsToLog[key]);
1278
+ }
1279
+ // TODO would be nice to use clause here. need update version of the queries so that
1280
+ // we don't have to handle dialect specifics here
1281
+ // can't use clause because of IS NULL
1282
+ // valsString.push(clause.Eq(key, val).clause(idx));
1283
+ if (dialect === db_1.Dialect.Postgres) {
1284
+ valsString.push(`${key} = $${idx}`);
1285
+ }
1286
+ else {
1287
+ valsString.push(`${key} = ?`);
1288
+ }
1289
+ idx++;
865
1290
  }
866
1291
  }
867
1292
  const vals = valsString.join(", ");
868
1293
  let query = `UPDATE ${options.tableName} SET ${vals} WHERE `;
869
- if (dialect === db_1.Dialect.Postgres) {
870
- query = query + `${options.key} = $${idx}`;
871
- }
872
- else {
873
- query = query + `${options.key} = ?`;
1294
+ query = query + options.whereClause.clause(idx);
1295
+ values.push(...options.whereClause.values());
1296
+ if (options.fieldsToLog) {
1297
+ logValues.push(...options.whereClause.logValues());
874
1298
  }
875
1299
  if (suffix) {
876
1300
  query = query + " " + suffix;
@@ -878,10 +1302,8 @@ function buildUpdateQuery(options, id, suffix) {
878
1302
  return [query, values, logValues];
879
1303
  }
880
1304
  exports.buildUpdateQuery = buildUpdateQuery;
881
- async function editRow(queryer, options, id, suffix) {
882
- const [query, values, logValues] = buildUpdateQuery(options, id, suffix);
883
- // add id as value to prepared query
884
- values.push(id);
1305
+ async function editRow(queryer, options, suffix) {
1306
+ const [query, values, logValues] = buildUpdateQuery(options, suffix);
885
1307
  const res = await mutateRow(queryer, query, values, logValues, options);
886
1308
  if (res?.rowCount == 1) {
887
1309
  // for now assume id primary key
@@ -892,10 +1314,8 @@ async function editRow(queryer, options, id, suffix) {
892
1314
  return null;
893
1315
  }
894
1316
  exports.editRow = editRow;
895
- function editRowSync(queryer, options, id, suffix) {
896
- const [query, values, logValues] = buildUpdateQuery(options, id, suffix);
897
- // add id as value to prepared query
898
- values.push(id);
1317
+ function editRowSync(queryer, options, suffix) {
1318
+ const [query, values, logValues] = buildUpdateQuery(options, suffix);
899
1319
  const res = mutateRowSync(queryer, query, values, logValues, options);
900
1320
  if (res?.rowCount == 1) {
901
1321
  // for now assume id primary key
@@ -946,21 +1366,22 @@ class AssocEdge {
946
1366
  this.edgeType = data.edge_type;
947
1367
  this.time = data.time;
948
1368
  this.data = data.data;
1369
+ this.rawData = data;
1370
+ }
1371
+ __getRawData() {
1372
+ // incase there's extra db fields. useful for tests
1373
+ // in production, a subclass of this should be in use so we won't need this...
1374
+ return this.rawData;
949
1375
  }
950
1376
  getCursor() {
951
1377
  return getCursor({
952
1378
  row: this,
953
- col: "time",
954
- conv: (t) => {
955
- if (typeof t === "string") {
956
- return Date.parse(t);
957
- }
958
- return t.getTime();
959
- },
1379
+ col: "id2",
960
1380
  });
961
1381
  }
962
1382
  }
963
1383
  exports.AssocEdge = AssocEdge;
1384
+ // TODO eventually update this for sortCol time unique keys
964
1385
  function getCursor(opts) {
965
1386
  const { row, col, conv } = opts;
966
1387
  // row: Data, col: string, conv?: (any) => any) {
@@ -1040,52 +1461,79 @@ const edgeFields = [
1040
1461
  ];
1041
1462
  exports.DefaultLimit = 1000;
1042
1463
  // TODO default limit from somewhere
1043
- function defaultEdgeQueryOptions(id1, edgeType) {
1464
+ function defaultEdgeQueryOptions(id1, edgeType, id2) {
1465
+ let cls = clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType));
1466
+ if (id2) {
1467
+ cls = clause.And(cls, clause.Eq("id2", id2));
1468
+ }
1044
1469
  return {
1045
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1470
+ clause: cls,
1046
1471
  orderby: "time DESC",
1047
1472
  limit: exports.DefaultLimit,
1048
1473
  };
1049
1474
  }
1050
- exports.defaultEdgeQueryOptions = defaultEdgeQueryOptions;
1051
1475
  async function loadEdges(options) {
1052
1476
  return loadCustomEdges({ ...options, ctr: AssocEdge });
1053
1477
  }
1054
1478
  exports.loadEdges = loadEdges;
1479
+ function getEdgeClauseAndFields(cls, options) {
1480
+ let fields = edgeFields;
1481
+ if (globalSchema?.transformEdgeRead) {
1482
+ const transformClause = globalSchema.transformEdgeRead();
1483
+ if (!options.disableTransformations) {
1484
+ cls = clause.And(cls, transformClause);
1485
+ }
1486
+ fields = edgeFields.concat(transformClause.columns());
1487
+ }
1488
+ return {
1489
+ cls,
1490
+ fields,
1491
+ };
1492
+ }
1493
+ exports.getEdgeClauseAndFields = getEdgeClauseAndFields;
1055
1494
  async function loadCustomEdges(options) {
1056
- const { id1, edgeType, context } = options;
1495
+ const { cls: actualClause, fields, defaultOptions, tableName, } = await loadEgesInfo(options);
1496
+ const rows = await loadRows({
1497
+ tableName,
1498
+ fields: fields,
1499
+ clause: actualClause,
1500
+ orderby: options.queryOptions?.orderby || defaultOptions.orderby,
1501
+ limit: options.queryOptions?.limit || defaultOptions.limit,
1502
+ context: options.context,
1503
+ });
1504
+ return rows.map((row) => {
1505
+ return new options.ctr(row);
1506
+ });
1507
+ }
1508
+ exports.loadCustomEdges = loadCustomEdges;
1509
+ async function loadEgesInfo(options, id2) {
1510
+ const { id1, edgeType } = options;
1057
1511
  const edgeData = await loadEdgeData(edgeType);
1058
1512
  if (!edgeData) {
1059
1513
  throw new Error(`error loading edge data for ${edgeType}`);
1060
1514
  }
1061
- const defaultOptions = defaultEdgeQueryOptions(id1, edgeType);
1515
+ const defaultOptions = defaultEdgeQueryOptions(id1, edgeType, id2);
1062
1516
  let cls = defaultOptions.clause;
1063
1517
  if (options.queryOptions?.clause) {
1064
1518
  cls = clause.And(cls, options.queryOptions.clause);
1065
1519
  }
1066
- const rows = await loadRows({
1520
+ return {
1521
+ ...getEdgeClauseAndFields(cls, options),
1522
+ defaultOptions,
1067
1523
  tableName: edgeData.edgeTable,
1068
- fields: edgeFields,
1069
- clause: cls,
1070
- orderby: options.queryOptions?.orderby || defaultOptions.orderby,
1071
- limit: options.queryOptions?.limit || defaultOptions.limit,
1072
- context,
1073
- });
1074
- return rows.map((row) => {
1075
- return new options.ctr(row);
1076
- });
1524
+ };
1077
1525
  }
1078
- exports.loadCustomEdges = loadCustomEdges;
1079
1526
  async function loadUniqueEdge(options) {
1080
1527
  const { id1, edgeType, context } = options;
1081
1528
  const edgeData = await loadEdgeData(edgeType);
1082
1529
  if (!edgeData) {
1083
1530
  throw new Error(`error loading edge data for ${edgeType}`);
1084
1531
  }
1532
+ const { cls, fields } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
1085
1533
  const row = await loadRow({
1086
1534
  tableName: edgeData.edgeTable,
1087
- fields: edgeFields,
1088
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1535
+ fields: fields,
1536
+ clause: cls,
1089
1537
  context,
1090
1538
  });
1091
1539
  if (!row) {
@@ -1112,21 +1560,28 @@ async function loadRawEdgeCountX(options) {
1112
1560
  if (!edgeData) {
1113
1561
  throw new Error(`error loading edge data for ${edgeType}`);
1114
1562
  }
1563
+ const { cls } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
1115
1564
  const row = await loadRowX({
1116
1565
  tableName: edgeData.edgeTable,
1117
1566
  // sqlite needs as count otherwise it returns count(1)
1118
1567
  fields: ["count(1) as count"],
1119
- clause: clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1568
+ clause: cls,
1120
1569
  context,
1121
1570
  });
1122
1571
  return parseInt(row["count"], 10) || 0;
1123
1572
  }
1124
1573
  exports.loadRawEdgeCountX = loadRawEdgeCountX;
1125
1574
  async function loadEdgeForID2(options) {
1126
- // 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
1127
- // we're assuming a cache here but not always true and this can be expensive if not...
1128
- const edges = await loadCustomEdges(options);
1129
- return edges.find((edge) => edge.id2 == options.id2);
1575
+ const { cls: actualClause, fields, tableName, } = await loadEgesInfo(options, options.id2);
1576
+ const row = await loadRow({
1577
+ tableName,
1578
+ fields: fields,
1579
+ clause: actualClause,
1580
+ context: options.context,
1581
+ });
1582
+ if (row) {
1583
+ return new options.ctr(row);
1584
+ }
1130
1585
  }
1131
1586
  exports.loadEdgeForID2 = loadEdgeForID2;
1132
1587
  async function loadNodesByEdge(viewer, id1, edgeType, options) {
@@ -1142,51 +1597,86 @@ async function loadNodesByEdge(viewer, id1, edgeType, options) {
1142
1597
  }
1143
1598
  exports.loadNodesByEdge = loadNodesByEdge;
1144
1599
  async function applyPrivacyPolicyForRow(viewer, options, row) {
1145
- if (!row) {
1146
- return null;
1147
- }
1148
- const ent = new options.ent(viewer, row);
1149
- return await applyPrivacyPolicyForEnt(viewer, ent);
1600
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
1601
+ return r instanceof Error ? null : r;
1150
1602
  }
1151
1603
  exports.applyPrivacyPolicyForRow = applyPrivacyPolicyForRow;
1604
+ async function applyPrivacyPolicyForRowImpl(viewer, options, row) {
1605
+ const ent = new options.ent(viewer, row);
1606
+ return applyPrivacyPolicyForEnt(viewer, ent, row, options);
1607
+ }
1152
1608
  async function applyPrivacyPolicyForRowX(viewer, options, row) {
1153
1609
  const ent = new options.ent(viewer, row);
1154
- return await applyPrivacyPolicyForEntX(viewer, ent);
1610
+ return await applyPrivacyPolicyForEntX(viewer, ent, row, options);
1155
1611
  }
1156
- exports.applyPrivacyPolicyForRowX = applyPrivacyPolicyForRowX;
1157
- async function applyPrivacyPolicyForRows(viewer, rows, options) {
1612
+ // deprecated. doesn't use entcache
1613
+ async function applyPrivacyPolicyForRowsDeprecated(viewer, rows, options) {
1158
1614
  let m = new Map();
1159
1615
  // apply privacy logic
1160
1616
  await Promise.all(rows.map(async (row) => {
1161
- const ent = new options.ent(viewer, row);
1162
- let privacyEnt = await applyPrivacyPolicyForEnt(viewer, ent);
1617
+ let privacyEnt = await applyPrivacyPolicyForRow(viewer, options, row);
1163
1618
  if (privacyEnt) {
1164
1619
  m.set(privacyEnt.id, privacyEnt);
1165
1620
  }
1166
1621
  }));
1167
1622
  return m;
1168
1623
  }
1169
- exports.applyPrivacyPolicyForRows = applyPrivacyPolicyForRows;
1170
- async function loadEdgeWithConst(viewer, id1, id2, edgeEnum, edgeType) {
1171
- const edge = await loadEdgeForID2({
1172
- id1: id1,
1173
- id2: id2,
1174
- edgeType: edgeType,
1175
- context: viewer.context,
1176
- ctr: AssocEdge,
1177
- });
1178
- return [edgeEnum, edge];
1624
+ async function applyPrivacyPolicyForRows(viewer, rows, options) {
1625
+ const result = new Array(rows.length);
1626
+ if (!rows.length) {
1627
+ return [];
1628
+ }
1629
+ const entLoader = getEntLoader(viewer, options);
1630
+ await Promise.all(rows.map(async (row, idx) => {
1631
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options, entLoader);
1632
+ if (r instanceof ErrorWrapper) {
1633
+ return;
1634
+ }
1635
+ result[idx] = r;
1636
+ }));
1637
+ // filter ents that aren't visible because of privacy
1638
+ return result.filter((r) => r !== undefined);
1179
1639
  }
1640
+ exports.applyPrivacyPolicyForRows = applyPrivacyPolicyForRows;
1180
1641
  // given a viewer, an id pair, and a map of edgeEnum to EdgeType
1181
1642
  // return the edgeEnum that's set in the group
1182
1643
  async function getEdgeTypeInGroup(viewer, id1, id2, m) {
1183
1644
  let promises = [];
1184
- for (const [k, v] of m) {
1185
- promises.push(loadEdgeWithConst(viewer, id1, id2, k, v));
1186
- }
1645
+ const edgeDatas = await loadEdgeDatas(...Array.from(m.values()));
1646
+ let tableToEdgeEnumMap = new Map();
1647
+ for (const [edgeEnum, edgeType] of m) {
1648
+ const edgeData = edgeDatas.get(edgeType);
1649
+ if (!edgeData) {
1650
+ throw new Error(`could not load edge data for '${edgeType}'`);
1651
+ }
1652
+ const l = tableToEdgeEnumMap.get(edgeData.edgeTable) ?? [];
1653
+ l.push(edgeEnum);
1654
+ tableToEdgeEnumMap.set(edgeData.edgeTable, l);
1655
+ }
1656
+ tableToEdgeEnumMap.forEach((edgeEnums, tableName) => {
1657
+ promises.push((async () => {
1658
+ const edgeTypes = edgeEnums.map((edgeEnum) => m.get(edgeEnum));
1659
+ const { cls, fields } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.In("edge_type", edgeTypes), clause.Eq("id2", id2)), {});
1660
+ const rows = await loadRows({
1661
+ tableName,
1662
+ fields,
1663
+ clause: cls,
1664
+ context: viewer.context,
1665
+ });
1666
+ const row = rows[0];
1667
+ if (row) {
1668
+ const edgeType = row.edge_type;
1669
+ for (const [k, v] of m) {
1670
+ if (v === edgeType) {
1671
+ return [k, new AssocEdge(row)];
1672
+ }
1673
+ }
1674
+ }
1675
+ })());
1676
+ });
1187
1677
  const results = await Promise.all(promises);
1188
1678
  for (const res of results) {
1189
- if (res[1]) {
1679
+ if (res && res[1]) {
1190
1680
  return [res[0], res[1]];
1191
1681
  }
1192
1682
  }