@snowtop/ent 0.1.0-alpha9 → 0.1.0-alpha90
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.
- package/action/action.d.ts +36 -31
- package/action/action.js +2 -6
- package/action/executor.d.ts +3 -3
- package/action/executor.js +2 -2
- package/action/experimental_action.d.ts +29 -22
- package/action/experimental_action.js +29 -6
- package/action/orchestrator.d.ts +38 -16
- package/action/orchestrator.js +219 -61
- package/action/privacy.d.ts +2 -2
- package/core/base.d.ts +45 -24
- package/core/base.js +7 -1
- package/core/clause.d.ts +68 -7
- package/core/clause.js +291 -62
- package/core/config.d.ts +8 -0
- package/core/context.d.ts +5 -3
- package/core/context.js +20 -2
- package/core/convert.d.ts +1 -1
- package/core/db.d.ts +2 -2
- package/core/db.js +1 -1
- package/core/ent.d.ts +79 -24
- package/core/ent.js +520 -168
- package/core/loaders/assoc_count_loader.d.ts +2 -2
- package/core/loaders/assoc_count_loader.js +6 -1
- package/core/loaders/assoc_edge_loader.d.ts +2 -2
- package/core/loaders/index.d.ts +1 -1
- package/core/loaders/index.js +1 -3
- package/core/loaders/index_loader.d.ts +2 -2
- package/core/loaders/loader.js +5 -5
- package/core/loaders/object_loader.d.ts +6 -5
- package/core/loaders/object_loader.js +62 -58
- package/core/loaders/query_loader.d.ts +6 -12
- package/core/loaders/query_loader.js +47 -10
- package/core/loaders/raw_count_loader.d.ts +2 -2
- package/core/logger.d.ts +1 -1
- package/core/logger.js +1 -0
- package/core/privacy.d.ts +26 -25
- package/core/privacy.js +21 -25
- package/core/query/assoc_query.d.ts +6 -6
- package/core/query/custom_clause_query.d.ts +24 -0
- package/core/query/custom_clause_query.js +72 -0
- package/core/query/custom_query.d.ts +20 -5
- package/core/query/custom_query.js +77 -10
- package/core/query/index.d.ts +1 -0
- package/core/query/index.js +3 -1
- package/core/query/query.d.ts +1 -1
- package/core/query/query.js +8 -1
- package/core/query/shared_assoc_test.d.ts +1 -1
- package/core/query/shared_assoc_test.js +17 -5
- package/core/query/shared_test.d.ts +3 -0
- package/core/query/shared_test.js +211 -30
- package/core/viewer.d.ts +3 -3
- package/core/viewer.js +1 -1
- package/graphql/graphql.js +6 -0
- package/graphql/query/edge_connection.d.ts +9 -9
- package/graphql/query/page_info.d.ts +1 -1
- package/index.d.ts +11 -5
- package/index.js +15 -6
- package/package.json +1 -1
- package/parse_schema/parse.d.ts +12 -3
- package/parse_schema/parse.js +70 -11
- package/schema/base_schema.js +3 -0
- package/schema/field.d.ts +44 -8
- package/schema/field.js +125 -9
- package/schema/index.d.ts +2 -2
- package/schema/json_field.d.ts +13 -1
- package/schema/json_field.js +28 -1
- package/schema/schema.d.ts +65 -11
- package/schema/schema.js +18 -4
- package/schema/struct_field.d.ts +11 -1
- package/schema/struct_field.js +44 -5
- package/scripts/custom_graphql.js +8 -3
- package/scripts/{transform_schema.d.ts → migrate_v0.1.d.ts} +0 -0
- package/scripts/migrate_v0.1.js +36 -0
- package/scripts/read_schema.js +15 -4
- package/testutils/builder.d.ts +31 -21
- package/testutils/builder.js +83 -29
- package/testutils/db/fixture.d.ts +10 -0
- package/testutils/db/fixture.js +26 -0
- package/testutils/db/{test_db.d.ts → temp_db.d.ts} +15 -3
- package/testutils/db/{test_db.js → temp_db.js} +70 -16
- package/testutils/db/value.d.ts +6 -0
- package/testutils/db/value.js +251 -0
- package/testutils/db_time_zone.d.ts +4 -0
- package/testutils/db_time_zone.js +41 -0
- package/testutils/fake_data/fake_contact.d.ts +5 -4
- package/testutils/fake_data/fake_contact.js +14 -6
- package/testutils/fake_data/fake_event.d.ts +5 -3
- package/testutils/fake_data/fake_event.js +8 -5
- package/testutils/fake_data/fake_user.d.ts +4 -4
- package/testutils/fake_data/fake_user.js +16 -13
- package/testutils/fake_data/test_helpers.d.ts +3 -2
- package/testutils/fake_data/test_helpers.js +8 -6
- package/testutils/fake_data/user_query.d.ts +8 -6
- package/testutils/fake_data/user_query.js +28 -21
- package/testutils/fake_log.d.ts +3 -3
- package/testutils/parse_sql.d.ts +6 -0
- package/testutils/parse_sql.js +16 -2
- package/testutils/test_edge_global_schema.d.ts +15 -0
- package/testutils/test_edge_global_schema.js +58 -0
- package/testutils/write.d.ts +2 -2
- package/testutils/write.js +29 -7
- package/tsc/ast.d.ts +44 -0
- package/tsc/ast.js +267 -0
- package/tsc/compilerOptions.d.ts +6 -0
- package/tsc/compilerOptions.js +40 -1
- package/tsc/move_generated.d.ts +1 -0
- package/tsc/move_generated.js +160 -0
- package/tsc/transform.d.ts +21 -0
- package/tsc/transform.js +167 -0
- package/tsc/transform_action.d.ts +22 -0
- package/tsc/transform_action.js +179 -0
- package/tsc/transform_ent.d.ts +17 -0
- package/tsc/transform_ent.js +59 -0
- package/tsc/transform_schema.d.ts +27 -0
- package/tsc/transform_schema.js +379 -0
- package/scripts/transform_schema.js +0 -445
package/core/ent.js
CHANGED
|
@@ -22,13 +22,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
22
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
23
|
};
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.
|
|
25
|
+
exports.loadRawEdgeCountX = exports.loadUniqueNode = exports.loadUniqueEdge = exports.loadCustomEdges = exports.getEdgeClauseAndFields = 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.__hasGlobalSchema = exports.clearGlobalSchema = exports.setGlobalSchema = exports.EditNodeOperation = exports.RawQueryOperation = exports.buildGroupQuery = exports.buildQuery = exports.loadRows = exports.performRawQuery = exports.loadRow = exports.loadRowX = exports.loadDerivedEntX = exports.loadDerivedEnt = exports.loadCustomCount = exports.loadCustomData = exports.loadCustomEnts = exports.loadEntsFromClause = exports.loadEntsList = exports.loadEnts = exports.loadEntXFromClause = exports.loadEntFromClause = exports.loadEntXViaKey = exports.loadEntX = exports.loadEntViaKey = exports.loadEnt = exports.getEntKey = void 0;
|
|
26
|
+
exports.getEdgeTypeInGroup = exports.applyPrivacyPolicyForRows = exports.applyPrivacyPolicyForRow = exports.loadNodesByEdge = exports.loadEdgeForID2 = void 0;
|
|
26
27
|
const db_1 = __importStar(require("./db"));
|
|
27
28
|
const privacy_1 = require("./privacy");
|
|
28
29
|
const clause = __importStar(require("./clause"));
|
|
29
30
|
const action_1 = require("../action");
|
|
30
31
|
const logger_1 = require("./logger");
|
|
31
32
|
const dataloader_1 = __importDefault(require("dataloader"));
|
|
33
|
+
const schema_1 = require("../schema/");
|
|
32
34
|
// TODO kill this and createDataLoader
|
|
33
35
|
class cacheMap {
|
|
34
36
|
constructor(options) {
|
|
@@ -38,7 +40,7 @@ class cacheMap {
|
|
|
38
40
|
get(key) {
|
|
39
41
|
const ret = this.m.get(key);
|
|
40
42
|
if (ret) {
|
|
41
|
-
(0, logger_1.log)("
|
|
43
|
+
(0, logger_1.log)("cache", {
|
|
42
44
|
"dataloader-cache-hit": key,
|
|
43
45
|
"tableName": this.options.tableName,
|
|
44
46
|
});
|
|
@@ -55,12 +57,41 @@ class cacheMap {
|
|
|
55
57
|
return this.m.clear();
|
|
56
58
|
}
|
|
57
59
|
}
|
|
60
|
+
class entCacheMap {
|
|
61
|
+
constructor(viewer, options) {
|
|
62
|
+
this.viewer = viewer;
|
|
63
|
+
this.options = options;
|
|
64
|
+
this.m = new Map();
|
|
65
|
+
this.logEnabled = false;
|
|
66
|
+
this.logEnabled = (0, logger_1.logEnabled)("cache");
|
|
67
|
+
}
|
|
68
|
+
get(id) {
|
|
69
|
+
const ret = this.m.get(id);
|
|
70
|
+
if (this.logEnabled && ret) {
|
|
71
|
+
const key = getEntKey(this.viewer, id, this.options);
|
|
72
|
+
(0, logger_1.log)("cache", {
|
|
73
|
+
"ent-cache-hit": key,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return ret;
|
|
77
|
+
}
|
|
78
|
+
set(key, value) {
|
|
79
|
+
return this.m.set(key, value);
|
|
80
|
+
}
|
|
81
|
+
delete(key) {
|
|
82
|
+
return this.m.delete(key);
|
|
83
|
+
}
|
|
84
|
+
clear() {
|
|
85
|
+
return this.m.clear();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
58
88
|
function createDataLoader(options) {
|
|
59
89
|
const loaderOptions = {};
|
|
60
90
|
// if query logging is enabled, we should log what's happening with loader
|
|
61
91
|
if ((0, logger_1.logEnabled)("query")) {
|
|
62
92
|
loaderOptions.cacheMap = new cacheMap(options);
|
|
63
93
|
}
|
|
94
|
+
// something here brokwn with strict:true
|
|
64
95
|
return new dataloader_1.default(async (ids) => {
|
|
65
96
|
if (!ids.length) {
|
|
66
97
|
return [];
|
|
@@ -84,28 +115,145 @@ function createDataLoader(options) {
|
|
|
84
115
|
return result;
|
|
85
116
|
}, loaderOptions);
|
|
86
117
|
}
|
|
87
|
-
//
|
|
118
|
+
// used to wrap errors that would eventually be thrown in ents
|
|
119
|
+
// not an Error because DataLoader automatically rejects that
|
|
120
|
+
class ErrorWrapper {
|
|
121
|
+
constructor(error) {
|
|
122
|
+
this.error = error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function createEntLoader(viewer, options, map) {
|
|
126
|
+
// share the cache across loaders even if we create a new instance
|
|
127
|
+
const loaderOptions = {};
|
|
128
|
+
loaderOptions.cacheMap = map;
|
|
129
|
+
return new dataloader_1.default(async (ids) => {
|
|
130
|
+
if (!ids.length) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
let result = [];
|
|
134
|
+
const loader = options.loaderFactory.createLoader(viewer.context);
|
|
135
|
+
const rows = await loader.loadMany(ids);
|
|
136
|
+
// this is a loader which should return the same order based on passed-in ids
|
|
137
|
+
// so let's depend on that...
|
|
138
|
+
for (let idx = 0; idx < rows.length; idx++) {
|
|
139
|
+
const row = rows[idx];
|
|
140
|
+
// db error
|
|
141
|
+
if (row instanceof Error) {
|
|
142
|
+
result[idx] = row;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
else if (!row) {
|
|
146
|
+
result[idx] = new ErrorWrapper(new Error(`couldn't find row for value ${ids[idx]}`));
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
|
|
150
|
+
if (r instanceof Error) {
|
|
151
|
+
result[idx] = new ErrorWrapper(r);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
result[idx] = r;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}, loaderOptions);
|
|
160
|
+
}
|
|
161
|
+
class EntLoader {
|
|
162
|
+
constructor(viewer, options) {
|
|
163
|
+
this.viewer = viewer;
|
|
164
|
+
this.options = options;
|
|
165
|
+
this.map = new entCacheMap(viewer, options);
|
|
166
|
+
this.loader = createEntLoader(this.viewer, this.options, this.map);
|
|
167
|
+
}
|
|
168
|
+
getMap() {
|
|
169
|
+
return this.map;
|
|
170
|
+
}
|
|
171
|
+
async load(id) {
|
|
172
|
+
return this.loader.load(id);
|
|
173
|
+
}
|
|
174
|
+
async loadMany(ids) {
|
|
175
|
+
return this.loader.loadMany(ids);
|
|
176
|
+
}
|
|
177
|
+
prime(id, ent) {
|
|
178
|
+
this.loader.prime(id, ent);
|
|
179
|
+
}
|
|
180
|
+
clear(id) {
|
|
181
|
+
this.loader.clear(id);
|
|
182
|
+
}
|
|
183
|
+
clearAll() {
|
|
184
|
+
this.loader.clearAll();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function getEntLoader(viewer, options) {
|
|
188
|
+
if (!viewer.context?.cache) {
|
|
189
|
+
return new EntLoader(viewer, options);
|
|
190
|
+
}
|
|
191
|
+
const name = `ent-loader:${viewer.instanceKey()}:${options.loaderFactory.name}`;
|
|
192
|
+
return viewer.context.cache.getLoaderWithLoadMany(name, () => new EntLoader(viewer, options));
|
|
193
|
+
}
|
|
194
|
+
function getEntKey(viewer, id, options) {
|
|
195
|
+
return `${viewer.instanceKey()}:${options.loaderFactory.name}:${id}`;
|
|
196
|
+
}
|
|
197
|
+
exports.getEntKey = getEntKey;
|
|
88
198
|
async function loadEnt(viewer, id, options) {
|
|
89
|
-
|
|
90
|
-
|
|
199
|
+
if (typeof id !== "string" &&
|
|
200
|
+
typeof id !== "number" &&
|
|
201
|
+
typeof id !== "bigint") {
|
|
202
|
+
throw new Error(`invalid id ${id} passed to loadEnt`);
|
|
203
|
+
}
|
|
204
|
+
const r = await getEntLoader(viewer, options).load(id);
|
|
205
|
+
return r instanceof ErrorWrapper ? null : r;
|
|
91
206
|
}
|
|
92
207
|
exports.loadEnt = loadEnt;
|
|
208
|
+
async function applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options,
|
|
209
|
+
// can pass in loader when calling this for multi-id cases...
|
|
210
|
+
loader) {
|
|
211
|
+
if (!loader) {
|
|
212
|
+
loader = getEntLoader(viewer, options);
|
|
213
|
+
}
|
|
214
|
+
// TODO every row.id needs to be audited...
|
|
215
|
+
// https://github.com/lolopinto/ent/issues/1064
|
|
216
|
+
const id = row.id;
|
|
217
|
+
// we should check the ent loader cache to see if this is already there
|
|
218
|
+
// 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
|
|
219
|
+
const result = loader.getMap().get(id);
|
|
220
|
+
if (result !== undefined) {
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
|
|
224
|
+
if (r instanceof Error) {
|
|
225
|
+
loader.prime(id, new ErrorWrapper(r));
|
|
226
|
+
return new ErrorWrapper(r);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
loader.prime(id, r);
|
|
230
|
+
return r;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
93
233
|
// this is the same implementation-wise (right now) as loadEnt. it's just clearer that it's not loaded via ID.
|
|
94
234
|
// used for load via email address etc
|
|
95
235
|
async function loadEntViaKey(viewer, key, options) {
|
|
96
236
|
const row = await options.loaderFactory
|
|
97
237
|
.createLoader(viewer.context)
|
|
98
238
|
.load(key);
|
|
99
|
-
|
|
239
|
+
if (!row) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options);
|
|
243
|
+
return r instanceof ErrorWrapper ? null : r;
|
|
100
244
|
}
|
|
101
245
|
exports.loadEntViaKey = loadEntViaKey;
|
|
102
246
|
async function loadEntX(viewer, id, options) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
throw new Error(
|
|
247
|
+
if (typeof id !== "string" &&
|
|
248
|
+
typeof id !== "number" &&
|
|
249
|
+
typeof id !== "bigint") {
|
|
250
|
+
throw new Error(`invalid id ${id} passed to loadEntX`);
|
|
107
251
|
}
|
|
108
|
-
|
|
252
|
+
const r = await getEntLoader(viewer, options).load(id);
|
|
253
|
+
if (r instanceof ErrorWrapper) {
|
|
254
|
+
throw r.error;
|
|
255
|
+
}
|
|
256
|
+
return r;
|
|
109
257
|
}
|
|
110
258
|
exports.loadEntX = loadEntX;
|
|
111
259
|
async function loadEntXViaKey(viewer, key, options) {
|
|
@@ -116,9 +264,16 @@ async function loadEntXViaKey(viewer, key, options) {
|
|
|
116
264
|
// todo make this better
|
|
117
265
|
throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${key}`);
|
|
118
266
|
}
|
|
119
|
-
|
|
267
|
+
const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options);
|
|
268
|
+
if (r instanceof ErrorWrapper) {
|
|
269
|
+
throw r.error;
|
|
270
|
+
}
|
|
271
|
+
return r;
|
|
120
272
|
}
|
|
121
273
|
exports.loadEntXViaKey = loadEntXViaKey;
|
|
274
|
+
/**
|
|
275
|
+
* @deprecated use loadCustomEnts
|
|
276
|
+
*/
|
|
122
277
|
async function loadEntFromClause(viewer, options, clause) {
|
|
123
278
|
const rowOptions = {
|
|
124
279
|
...options,
|
|
@@ -126,12 +281,18 @@ async function loadEntFromClause(viewer, options, clause) {
|
|
|
126
281
|
context: viewer.context,
|
|
127
282
|
};
|
|
128
283
|
const row = await loadRow(rowOptions);
|
|
129
|
-
|
|
284
|
+
if (row === null) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
return applyPrivacyPolicyForRow(viewer, options, row);
|
|
130
288
|
}
|
|
131
289
|
exports.loadEntFromClause = loadEntFromClause;
|
|
132
290
|
// same as loadEntFromClause
|
|
133
291
|
// only works for ents where primary key is "id"
|
|
134
292
|
// use loadEnt with a loaderFactory if different
|
|
293
|
+
/**
|
|
294
|
+
* @deprecated use loadCustomEnts
|
|
295
|
+
*/
|
|
135
296
|
async function loadEntXFromClause(viewer, options, clause) {
|
|
136
297
|
const rowOptions = {
|
|
137
298
|
...options,
|
|
@@ -146,37 +307,19 @@ async function loadEnts(viewer, options, ...ids) {
|
|
|
146
307
|
if (!ids.length) {
|
|
147
308
|
return new Map();
|
|
148
309
|
}
|
|
149
|
-
|
|
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
|
|
310
|
+
// result
|
|
158
311
|
let m = new Map();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (row instanceof Error) {
|
|
166
|
-
throw row;
|
|
167
|
-
}
|
|
168
|
-
rows2.push(row);
|
|
312
|
+
const ret = await getEntLoader(viewer, options).loadMany(ids);
|
|
313
|
+
for (const r of ret) {
|
|
314
|
+
if (r instanceof Error) {
|
|
315
|
+
throw r;
|
|
169
316
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
m
|
|
174
|
-
// this is always "id" if not using an ObjectLoaderFactory
|
|
175
|
-
clause.In("id", ...ids), options);
|
|
317
|
+
if (r instanceof ErrorWrapper) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
m.set(r.id, r);
|
|
176
321
|
}
|
|
177
322
|
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
323
|
}
|
|
181
324
|
exports.loadEnts = loadEnts;
|
|
182
325
|
// calls loadEnts and returns the results sorted in the order they were passed in
|
|
@@ -195,6 +338,9 @@ async function loadEntsList(viewer, options, ...ids) {
|
|
|
195
338
|
exports.loadEntsList = loadEntsList;
|
|
196
339
|
// we return a map here so that any sorting for queries that exist
|
|
197
340
|
// can be done in O(N) time
|
|
341
|
+
/**
|
|
342
|
+
* @deperecated use loadCustomEnts
|
|
343
|
+
*/
|
|
198
344
|
async function loadEntsFromClause(viewer, clause, options) {
|
|
199
345
|
const rowOptions = {
|
|
200
346
|
...options,
|
|
@@ -202,65 +348,135 @@ async function loadEntsFromClause(viewer, clause, options) {
|
|
|
202
348
|
context: viewer.context,
|
|
203
349
|
};
|
|
204
350
|
const rows = await loadRows(rowOptions);
|
|
205
|
-
return
|
|
351
|
+
return applyPrivacyPolicyForRowsDeprecated(viewer, rows, options);
|
|
206
352
|
}
|
|
207
353
|
exports.loadEntsFromClause = loadEntsFromClause;
|
|
208
354
|
async function loadCustomEnts(viewer, options, query) {
|
|
209
355
|
const rows = await loadCustomData(options, query, viewer.context);
|
|
210
|
-
|
|
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);
|
|
356
|
+
return applyPrivacyPolicyForRows(viewer, rows, options);
|
|
220
357
|
}
|
|
221
358
|
exports.loadCustomEnts = loadCustomEnts;
|
|
222
359
|
function isClause(opts) {
|
|
223
360
|
const cls = opts;
|
|
224
361
|
return cls.clause !== undefined && cls.values !== undefined;
|
|
225
362
|
}
|
|
226
|
-
function
|
|
363
|
+
function isParameterizedQuery(opts) {
|
|
227
364
|
return opts.query !== undefined;
|
|
228
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Note that if there's default read transformations (e.g. soft delete) and a clause is passed in
|
|
368
|
+
* either as Clause or QueryDataOptions without {disableTransformations: true}, the default transformation
|
|
369
|
+
* (e.g. soft delete) is applied.
|
|
370
|
+
*
|
|
371
|
+
* Passing a full SQL string or Paramterized SQL string doesn't apply it and the given string is sent to the
|
|
372
|
+
* database as written.
|
|
373
|
+
*
|
|
374
|
+
* e.g.
|
|
375
|
+
* Foo.loadCustom(opts, 'SELECT * FROM foo') // doesn't change the query
|
|
376
|
+
* Foo.loadCustom(opts, { query: 'SELECT * FROM foo WHERE id = ?', values: [1]}) // doesn't change the query
|
|
377
|
+
* Foo.loadCustom(opts, query.Eq('time', Date.now())) // changes the query
|
|
378
|
+
* Foo.loadCustom(opts, {
|
|
379
|
+
* clause: query.LessEq('time', Date.now()),
|
|
380
|
+
* limit: 100,
|
|
381
|
+
* orderby: 'time',
|
|
382
|
+
* }) // changes the query
|
|
383
|
+
* Foo.loadCustom(opts, {
|
|
384
|
+
* clause: query.LessEq('time', Date.now()),
|
|
385
|
+
* limit: 100,
|
|
386
|
+
* orderby: 'time',
|
|
387
|
+
* disableTransformations: false
|
|
388
|
+
* }) // doesn't change the query
|
|
389
|
+
*/
|
|
229
390
|
async function loadCustomData(options, query, context) {
|
|
391
|
+
const rows = await loadCustomDataImpl(options, query, context);
|
|
392
|
+
// prime the data so that subsequent fetches of the row with this id are a cache hit.
|
|
393
|
+
if (options.prime) {
|
|
394
|
+
const loader = options.loaderFactory.createLoader(context);
|
|
395
|
+
if (isPrimableLoader(loader) && loader.primeAll !== undefined) {
|
|
396
|
+
for (const row of rows) {
|
|
397
|
+
loader.primeAll(row);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return rows;
|
|
402
|
+
}
|
|
403
|
+
exports.loadCustomData = loadCustomData;
|
|
404
|
+
// NOTE: if you use a raw query or paramterized query with this,
|
|
405
|
+
// you should use `SELECT count(*) as count...`
|
|
406
|
+
async function loadCustomCount(options, query, context) {
|
|
407
|
+
// TODO also need to loaderify this in case we're querying for this a lot...
|
|
408
|
+
const rows = await loadCustomDataImpl({
|
|
409
|
+
...options,
|
|
410
|
+
fields: ["count(1) as count"],
|
|
411
|
+
}, query, context);
|
|
412
|
+
if (rows.length) {
|
|
413
|
+
return parseInt(rows[0].count);
|
|
414
|
+
}
|
|
415
|
+
return 0;
|
|
416
|
+
}
|
|
417
|
+
exports.loadCustomCount = loadCustomCount;
|
|
418
|
+
function isPrimableLoader(loader) {
|
|
419
|
+
return loader != undefined;
|
|
420
|
+
}
|
|
421
|
+
async function loadCustomDataImpl(options, query, context) {
|
|
422
|
+
function getClause(cls) {
|
|
423
|
+
let optClause = options.loaderFactory?.options?.clause;
|
|
424
|
+
if (typeof optClause === "function") {
|
|
425
|
+
optClause = optClause();
|
|
426
|
+
}
|
|
427
|
+
if (!optClause) {
|
|
428
|
+
return cls;
|
|
429
|
+
}
|
|
430
|
+
return clause.And(cls, optClause);
|
|
431
|
+
}
|
|
230
432
|
if (typeof query === "string") {
|
|
231
433
|
// no caching, perform raw query
|
|
232
|
-
return
|
|
434
|
+
return performRawQuery(query, [], []);
|
|
233
435
|
}
|
|
234
436
|
else if (isClause(query)) {
|
|
437
|
+
// if a Clause is passed in and we have a default clause
|
|
438
|
+
// associated with the query, pass that in
|
|
439
|
+
// if we want to disableTransformations, need to indicate that with
|
|
440
|
+
// disableTransformations option
|
|
235
441
|
// this will have rudimentary caching but nothing crazy
|
|
236
|
-
return
|
|
442
|
+
return loadRows({
|
|
237
443
|
...options,
|
|
238
|
-
clause: query,
|
|
444
|
+
clause: getClause(query),
|
|
239
445
|
context: context,
|
|
240
446
|
});
|
|
241
447
|
}
|
|
242
|
-
else if (
|
|
448
|
+
else if (isParameterizedQuery(query)) {
|
|
243
449
|
// no caching, perform raw query
|
|
244
|
-
return
|
|
450
|
+
return performRawQuery(query.query, query.values || [], query.logValues);
|
|
245
451
|
}
|
|
246
452
|
else {
|
|
453
|
+
let cls = query.clause;
|
|
454
|
+
if (!query.disableTransformations) {
|
|
455
|
+
cls = getClause(cls);
|
|
456
|
+
}
|
|
247
457
|
// this will have rudimentary caching but nothing crazy
|
|
248
|
-
return
|
|
458
|
+
return loadRows({
|
|
249
459
|
...query,
|
|
250
460
|
...options,
|
|
251
461
|
context: context,
|
|
462
|
+
clause: cls,
|
|
252
463
|
});
|
|
253
464
|
}
|
|
254
465
|
}
|
|
255
|
-
exports.loadCustomData = loadCustomData;
|
|
256
466
|
// Derived ents
|
|
467
|
+
// no ent caching
|
|
257
468
|
async function loadDerivedEnt(viewer, data, loader) {
|
|
258
469
|
const ent = new loader(viewer, data);
|
|
259
|
-
|
|
470
|
+
const r = await applyPrivacyPolicyForEnt(viewer, ent, data, {
|
|
260
471
|
ent: loader,
|
|
261
472
|
});
|
|
473
|
+
if (r instanceof Error) {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
return r;
|
|
262
477
|
}
|
|
263
478
|
exports.loadDerivedEnt = loadDerivedEnt;
|
|
479
|
+
// won't have caching yet either
|
|
264
480
|
async function loadDerivedEntX(viewer, data, loader) {
|
|
265
481
|
const ent = new loader(viewer, data);
|
|
266
482
|
return await applyPrivacyPolicyForEntX(viewer, ent, data, { ent: loader });
|
|
@@ -269,19 +485,21 @@ exports.loadDerivedEntX = loadDerivedEntX;
|
|
|
269
485
|
// everything calls into this two so should be fine
|
|
270
486
|
// TODO is there a smarter way to not instantiate two objects here?
|
|
271
487
|
async function applyPrivacyPolicyForEnt(viewer, ent, data, fieldPrivacyOptions) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (!visible) {
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
488
|
+
const error = await (0, privacy_1.applyPrivacyPolicyImpl)(viewer, ent.getPrivacyPolicy(), ent);
|
|
489
|
+
if (error === null) {
|
|
277
490
|
return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
|
|
278
491
|
}
|
|
279
|
-
return
|
|
492
|
+
return error;
|
|
280
493
|
}
|
|
281
494
|
async function applyPrivacyPolicyForEntX(viewer, ent, data, options) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
495
|
+
const r = await applyPrivacyPolicyForEnt(viewer, ent, data, options);
|
|
496
|
+
if (r instanceof Error) {
|
|
497
|
+
throw r;
|
|
498
|
+
}
|
|
499
|
+
if (r === null) {
|
|
500
|
+
throw new Error(`couldn't apply privacyPoliy for ent ${ent.id}`);
|
|
501
|
+
}
|
|
502
|
+
return r;
|
|
285
503
|
}
|
|
286
504
|
async function doFieldPrivacy(viewer, ent, data, options) {
|
|
287
505
|
if (!options.fieldPrivacy) {
|
|
@@ -290,12 +508,12 @@ async function doFieldPrivacy(viewer, ent, data, options) {
|
|
|
290
508
|
const promises = [];
|
|
291
509
|
let somethingChanged = false;
|
|
292
510
|
for (const [k, policy] of options.fieldPrivacy) {
|
|
511
|
+
const curr = data[k];
|
|
512
|
+
if (curr === null || curr === undefined) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
293
515
|
promises.push((async () => {
|
|
294
516
|
// 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
517
|
const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
|
|
300
518
|
if (!r) {
|
|
301
519
|
data[k] = null;
|
|
@@ -339,42 +557,27 @@ async function loadRow(options) {
|
|
|
339
557
|
}
|
|
340
558
|
const query = buildQuery(options);
|
|
341
559
|
logQuery(query, options.clause.logValues());
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (res.rowCount
|
|
346
|
-
|
|
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]);
|
|
560
|
+
const pool = db_1.default.getInstance().getPool();
|
|
561
|
+
const res = await pool.query(query, options.clause.values());
|
|
562
|
+
if (res.rowCount != 1) {
|
|
563
|
+
if (res.rowCount > 1) {
|
|
564
|
+
(0, logger_1.log)("error", "got more than one row for query " + query);
|
|
354
565
|
}
|
|
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
566
|
return null;
|
|
362
567
|
}
|
|
568
|
+
// put the row in the cache...
|
|
569
|
+
if (cache) {
|
|
570
|
+
cache.primeCache(options, res.rows[0]);
|
|
571
|
+
}
|
|
572
|
+
return res.rows[0];
|
|
363
573
|
}
|
|
364
574
|
exports.loadRow = loadRow;
|
|
365
575
|
// this always goes to the db, no cache, nothing
|
|
366
576
|
async function performRawQuery(query, values, logValues) {
|
|
367
577
|
const pool = db_1.default.getInstance().getPool();
|
|
368
578
|
logQuery(query, logValues || []);
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
return res.rows;
|
|
372
|
-
}
|
|
373
|
-
catch (e) {
|
|
374
|
-
// TODO need to change every query to catch an error!
|
|
375
|
-
(0, logger_1.log)("error", e);
|
|
376
|
-
return [];
|
|
377
|
-
}
|
|
579
|
+
const res = await pool.queryAll(query, values);
|
|
580
|
+
return res.rows;
|
|
378
581
|
}
|
|
379
582
|
exports.performRawQuery = performRawQuery;
|
|
380
583
|
// TODO this should throw, we can't be hiding errors here
|
|
@@ -432,10 +635,41 @@ function buildGroupQuery(options) {
|
|
|
432
635
|
];
|
|
433
636
|
}
|
|
434
637
|
exports.buildGroupQuery = buildGroupQuery;
|
|
638
|
+
class RawQueryOperation {
|
|
639
|
+
constructor(queries) {
|
|
640
|
+
this.queries = queries;
|
|
641
|
+
}
|
|
642
|
+
async performWrite(queryer, context) {
|
|
643
|
+
for (const q of this.queries) {
|
|
644
|
+
if (typeof q === "string") {
|
|
645
|
+
logQuery(q, []);
|
|
646
|
+
await queryer.query(q);
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
logQuery(q.query, q.logValues || []);
|
|
650
|
+
await queryer.query(q.query, q.values);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
performWriteSync(queryer, context) {
|
|
655
|
+
for (const q of this.queries) {
|
|
656
|
+
if (typeof q === "string") {
|
|
657
|
+
logQuery(q, []);
|
|
658
|
+
queryer.execSync(q);
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
logQuery(q.query, q.logValues || []);
|
|
662
|
+
queryer.execSync(q.query, q.values);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
exports.RawQueryOperation = RawQueryOperation;
|
|
435
668
|
class EditNodeOperation {
|
|
436
669
|
constructor(options, existingEnt = null) {
|
|
437
670
|
this.options = options;
|
|
438
671
|
this.existingEnt = existingEnt;
|
|
672
|
+
this.row = null;
|
|
439
673
|
this.placeholderID = options.placeholderID;
|
|
440
674
|
}
|
|
441
675
|
resolve(executor) {
|
|
@@ -471,9 +705,10 @@ class EditNodeOperation {
|
|
|
471
705
|
if (this.hasData(options.fields)) {
|
|
472
706
|
// even this with returning * may not always work if transformed...
|
|
473
707
|
// we can have a transformed flag to see if it should be returned?
|
|
474
|
-
this.row = await editRow(queryer, options,
|
|
708
|
+
this.row = await editRow(queryer, options, "RETURNING *");
|
|
475
709
|
}
|
|
476
710
|
else {
|
|
711
|
+
// @ts-ignore
|
|
477
712
|
this.row = this.existingEnt["data"];
|
|
478
713
|
}
|
|
479
714
|
}
|
|
@@ -496,7 +731,7 @@ class EditNodeOperation {
|
|
|
496
731
|
optionClause = opts.clause;
|
|
497
732
|
}
|
|
498
733
|
if (optionClause) {
|
|
499
|
-
cls = clause.And(
|
|
734
|
+
cls = clause.And(cls, optionClause);
|
|
500
735
|
}
|
|
501
736
|
}
|
|
502
737
|
const query = buildQuery({
|
|
@@ -520,10 +755,11 @@ class EditNodeOperation {
|
|
|
520
755
|
};
|
|
521
756
|
if (this.existingEnt) {
|
|
522
757
|
if (this.hasData(this.options.fields)) {
|
|
523
|
-
editRowSync(queryer, options,
|
|
758
|
+
editRowSync(queryer, options, "RETURNING *");
|
|
524
759
|
this.reloadRow(queryer, this.existingEnt.id, options);
|
|
525
760
|
}
|
|
526
761
|
else {
|
|
762
|
+
// @ts-ignore
|
|
527
763
|
this.row = this.existingEnt["data"];
|
|
528
764
|
}
|
|
529
765
|
}
|
|
@@ -544,8 +780,23 @@ class EditNodeOperation {
|
|
|
544
780
|
}
|
|
545
781
|
}
|
|
546
782
|
exports.EditNodeOperation = EditNodeOperation;
|
|
783
|
+
let globalSchema;
|
|
784
|
+
function setGlobalSchema(val) {
|
|
785
|
+
globalSchema = val;
|
|
786
|
+
}
|
|
787
|
+
exports.setGlobalSchema = setGlobalSchema;
|
|
788
|
+
function clearGlobalSchema() {
|
|
789
|
+
globalSchema = undefined;
|
|
790
|
+
}
|
|
791
|
+
exports.clearGlobalSchema = clearGlobalSchema;
|
|
792
|
+
// used by tests. no guarantee will always exist
|
|
793
|
+
function __hasGlobalSchema() {
|
|
794
|
+
return globalSchema !== undefined;
|
|
795
|
+
}
|
|
796
|
+
exports.__hasGlobalSchema = __hasGlobalSchema;
|
|
547
797
|
class EdgeOperation {
|
|
548
|
-
constructor(edgeInput, options) {
|
|
798
|
+
constructor(builder, edgeInput, options) {
|
|
799
|
+
this.builder = builder;
|
|
549
800
|
this.edgeInput = edgeInput;
|
|
550
801
|
this.options = options;
|
|
551
802
|
}
|
|
@@ -581,7 +832,31 @@ class EdgeOperation {
|
|
|
581
832
|
}
|
|
582
833
|
}
|
|
583
834
|
getDeleteRowParams(edgeData, edge, context) {
|
|
835
|
+
let transformed = null;
|
|
836
|
+
let op = schema_1.SQLStatementOperation.Delete;
|
|
837
|
+
let updateData = null;
|
|
838
|
+
// TODO respect disableTransformations
|
|
839
|
+
if (globalSchema?.transformEdgeWrite) {
|
|
840
|
+
transformed = globalSchema.transformEdgeWrite({
|
|
841
|
+
op: schema_1.SQLStatementOperation.Delete,
|
|
842
|
+
edge,
|
|
843
|
+
});
|
|
844
|
+
if (transformed) {
|
|
845
|
+
op = transformed.op;
|
|
846
|
+
if (transformed.op === schema_1.SQLStatementOperation.Insert) {
|
|
847
|
+
throw new Error(`cannot currently transform a delete into an insert`);
|
|
848
|
+
}
|
|
849
|
+
if (transformed.op === schema_1.SQLStatementOperation.Update) {
|
|
850
|
+
if (!transformed.data) {
|
|
851
|
+
throw new Error(`cannot transform a delete into an update without providing data`);
|
|
852
|
+
}
|
|
853
|
+
updateData = transformed.data;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
584
857
|
return {
|
|
858
|
+
op,
|
|
859
|
+
updateData,
|
|
585
860
|
options: {
|
|
586
861
|
tableName: edgeData.edgeTable,
|
|
587
862
|
context,
|
|
@@ -591,11 +866,35 @@ class EdgeOperation {
|
|
|
591
866
|
}
|
|
592
867
|
async performDeleteWrite(q, edgeData, edge, context) {
|
|
593
868
|
const params = this.getDeleteRowParams(edgeData, edge, context);
|
|
594
|
-
|
|
869
|
+
if (params.op === schema_1.SQLStatementOperation.Delete) {
|
|
870
|
+
return deleteRows(q, params.options, params.clause);
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
if (params.op !== schema_1.SQLStatementOperation.Update) {
|
|
874
|
+
throw new Error(`invalid operation ${params.op}`);
|
|
875
|
+
}
|
|
876
|
+
await editRow(q, {
|
|
877
|
+
tableName: params.options.tableName,
|
|
878
|
+
whereClause: params.clause,
|
|
879
|
+
fields: params.updateData,
|
|
880
|
+
});
|
|
881
|
+
}
|
|
595
882
|
}
|
|
596
883
|
performDeleteWriteSync(q, edgeData, edge, context) {
|
|
597
884
|
const params = this.getDeleteRowParams(edgeData, edge, context);
|
|
598
|
-
|
|
885
|
+
if (params.op === schema_1.SQLStatementOperation.Delete) {
|
|
886
|
+
return deleteRowsSync(q, params.options, params.clause);
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
if (params.op !== schema_1.SQLStatementOperation.Update) {
|
|
890
|
+
throw new Error(`invalid operation ${params.op}`);
|
|
891
|
+
}
|
|
892
|
+
editRowSync(q, {
|
|
893
|
+
tableName: params.options.tableName,
|
|
894
|
+
whereClause: params.clause,
|
|
895
|
+
fields: params.updateData,
|
|
896
|
+
});
|
|
897
|
+
}
|
|
599
898
|
}
|
|
600
899
|
getInsertRowParams(edgeData, edge, context) {
|
|
601
900
|
const fields = {
|
|
@@ -614,6 +913,30 @@ class EdgeOperation {
|
|
|
614
913
|
// maybe when actions exist?
|
|
615
914
|
fields["time"] = new Date().toISOString();
|
|
616
915
|
}
|
|
916
|
+
const onConflictFields = ["data"];
|
|
917
|
+
if (globalSchema?.extraEdgeFields) {
|
|
918
|
+
for (const name in globalSchema.extraEdgeFields) {
|
|
919
|
+
const f = globalSchema.extraEdgeFields[name];
|
|
920
|
+
if (f.defaultValueOnCreate) {
|
|
921
|
+
const storageKey = (0, schema_1.getStorageKey)(f, name);
|
|
922
|
+
fields[storageKey] = f.defaultValueOnCreate(this.builder, {});
|
|
923
|
+
// onconflict make sure we override the default values
|
|
924
|
+
// e.g. setting deleted_at = null for soft delete
|
|
925
|
+
onConflictFields.push(storageKey);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
// TODO respect disableTransformations
|
|
930
|
+
let transformed = null;
|
|
931
|
+
if (globalSchema?.transformEdgeWrite) {
|
|
932
|
+
transformed = globalSchema.transformEdgeWrite({
|
|
933
|
+
op: schema_1.SQLStatementOperation.Insert,
|
|
934
|
+
edge,
|
|
935
|
+
});
|
|
936
|
+
if (transformed) {
|
|
937
|
+
throw new Error(`transforming an insert edge not currently supported`);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
617
940
|
return [
|
|
618
941
|
{
|
|
619
942
|
tableName: edgeData.edgeTable,
|
|
@@ -621,7 +944,9 @@ class EdgeOperation {
|
|
|
621
944
|
fieldsToLog: fields,
|
|
622
945
|
context,
|
|
623
946
|
},
|
|
624
|
-
|
|
947
|
+
`ON CONFLICT(id1, edge_type, id2) DO UPDATE SET ${onConflictFields
|
|
948
|
+
.map((f) => `${f} = EXCLUDED.${f}`)
|
|
949
|
+
.join(", ")}`,
|
|
625
950
|
];
|
|
626
951
|
}
|
|
627
952
|
async performInsertWrite(q, edgeData, edge, context) {
|
|
@@ -658,7 +983,7 @@ class EdgeOperation {
|
|
|
658
983
|
}
|
|
659
984
|
}
|
|
660
985
|
symmetricEdge() {
|
|
661
|
-
return new EdgeOperation({
|
|
986
|
+
return new EdgeOperation(this.builder, {
|
|
662
987
|
id1: this.edgeInput.id2,
|
|
663
988
|
id1Type: this.edgeInput.id2Type,
|
|
664
989
|
id2: this.edgeInput.id1,
|
|
@@ -674,7 +999,7 @@ class EdgeOperation {
|
|
|
674
999
|
});
|
|
675
1000
|
}
|
|
676
1001
|
inverseEdge(edgeData) {
|
|
677
|
-
return new EdgeOperation({
|
|
1002
|
+
return new EdgeOperation(this.builder, {
|
|
678
1003
|
id1: this.edgeInput.id2,
|
|
679
1004
|
id1Type: this.edgeInput.id2Type,
|
|
680
1005
|
id2: this.edgeInput.id1,
|
|
@@ -742,7 +1067,7 @@ class EdgeOperation {
|
|
|
742
1067
|
if (data) {
|
|
743
1068
|
edge.data = data;
|
|
744
1069
|
}
|
|
745
|
-
return new EdgeOperation(edge, {
|
|
1070
|
+
return new EdgeOperation(builder, edge, {
|
|
746
1071
|
operation: action_1.WriteOperation.Insert,
|
|
747
1072
|
id2Placeholder,
|
|
748
1073
|
id1Placeholder,
|
|
@@ -763,7 +1088,7 @@ class EdgeOperation {
|
|
|
763
1088
|
if (data) {
|
|
764
1089
|
edge.data = data;
|
|
765
1090
|
}
|
|
766
|
-
return new EdgeOperation(edge, {
|
|
1091
|
+
return new EdgeOperation(builder, edge, {
|
|
767
1092
|
operation: action_1.WriteOperation.Insert,
|
|
768
1093
|
id1Placeholder,
|
|
769
1094
|
id2Placeholder,
|
|
@@ -781,7 +1106,7 @@ class EdgeOperation {
|
|
|
781
1106
|
id2Type: "",
|
|
782
1107
|
id1Type: "",
|
|
783
1108
|
};
|
|
784
|
-
return new EdgeOperation(edge, {
|
|
1109
|
+
return new EdgeOperation(builder, edge, {
|
|
785
1110
|
operation: action_1.WriteOperation.Delete,
|
|
786
1111
|
});
|
|
787
1112
|
}
|
|
@@ -796,7 +1121,7 @@ class EdgeOperation {
|
|
|
796
1121
|
id2Type: "",
|
|
797
1122
|
id1Type: "",
|
|
798
1123
|
};
|
|
799
|
-
return new EdgeOperation(edge, {
|
|
1124
|
+
return new EdgeOperation(builder, edge, {
|
|
800
1125
|
operation: action_1.WriteOperation.Delete,
|
|
801
1126
|
});
|
|
802
1127
|
}
|
|
@@ -808,40 +1133,26 @@ function isSyncQueryer(queryer) {
|
|
|
808
1133
|
async function mutateRow(queryer, query, values, logValues, options) {
|
|
809
1134
|
logQuery(query, logValues);
|
|
810
1135
|
let cache = options.context?.cache;
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
res = queryer.execSync(query, values);
|
|
815
|
-
}
|
|
816
|
-
else {
|
|
817
|
-
res = await queryer.exec(query, values);
|
|
818
|
-
}
|
|
819
|
-
if (cache) {
|
|
820
|
-
cache.clearCache();
|
|
821
|
-
}
|
|
822
|
-
return res;
|
|
1136
|
+
let res;
|
|
1137
|
+
if (isSyncQueryer(queryer)) {
|
|
1138
|
+
res = queryer.execSync(query, values);
|
|
823
1139
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1140
|
+
else {
|
|
1141
|
+
res = await queryer.exec(query, values);
|
|
1142
|
+
}
|
|
1143
|
+
if (cache) {
|
|
1144
|
+
cache.clearCache();
|
|
828
1145
|
}
|
|
1146
|
+
return res;
|
|
829
1147
|
}
|
|
830
1148
|
function mutateRowSync(queryer, query, values, logValues, options) {
|
|
831
1149
|
logQuery(query, logValues);
|
|
832
1150
|
let cache = options.context?.cache;
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
cache.clearCache();
|
|
837
|
-
}
|
|
838
|
-
return res;
|
|
839
|
-
}
|
|
840
|
-
catch (err) {
|
|
841
|
-
// TODO:::why is this not rethrowing?
|
|
842
|
-
(0, logger_1.log)("error", err);
|
|
843
|
-
throw err;
|
|
1151
|
+
const res = queryer.execSync(query, values);
|
|
1152
|
+
if (cache) {
|
|
1153
|
+
cache.clearCache();
|
|
844
1154
|
}
|
|
1155
|
+
return res;
|
|
845
1156
|
}
|
|
846
1157
|
function buildInsertQuery(options, suffix) {
|
|
847
1158
|
let fields = [];
|
|
@@ -893,32 +1204,36 @@ function createRowSync(queryer, options, suffix) {
|
|
|
893
1204
|
return null;
|
|
894
1205
|
}
|
|
895
1206
|
exports.createRowSync = createRowSync;
|
|
896
|
-
function buildUpdateQuery(options,
|
|
1207
|
+
function buildUpdateQuery(options, suffix) {
|
|
897
1208
|
let valsString = [];
|
|
898
1209
|
let values = [];
|
|
899
1210
|
let logValues = [];
|
|
900
1211
|
const dialect = db_1.default.getDialect();
|
|
901
1212
|
let idx = 1;
|
|
902
1213
|
for (const key in options.fields) {
|
|
903
|
-
|
|
1214
|
+
const val = options.fields[key];
|
|
1215
|
+
values.push(val);
|
|
904
1216
|
if (options.fieldsToLog) {
|
|
905
1217
|
logValues.push(options.fieldsToLog[key]);
|
|
906
1218
|
}
|
|
1219
|
+
// TODO would be nice to use clause here. need update version of the queries so that
|
|
1220
|
+
// we don't have to handle dialect specifics here
|
|
1221
|
+
// can't use clause because of IS NULL
|
|
1222
|
+
// valsString.push(clause.Eq(key, val).clause(idx));
|
|
907
1223
|
if (dialect === db_1.Dialect.Postgres) {
|
|
908
1224
|
valsString.push(`${key} = $${idx}`);
|
|
909
|
-
idx++;
|
|
910
1225
|
}
|
|
911
1226
|
else {
|
|
912
1227
|
valsString.push(`${key} = ?`);
|
|
913
1228
|
}
|
|
1229
|
+
idx++;
|
|
914
1230
|
}
|
|
915
1231
|
const vals = valsString.join(", ");
|
|
916
1232
|
let query = `UPDATE ${options.tableName} SET ${vals} WHERE `;
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
query = query + `${options.key} = ?`;
|
|
1233
|
+
query = query + options.whereClause.clause(idx);
|
|
1234
|
+
values.push(...options.whereClause.values());
|
|
1235
|
+
if (options.fieldsToLog) {
|
|
1236
|
+
logValues.push(...options.whereClause.logValues());
|
|
922
1237
|
}
|
|
923
1238
|
if (suffix) {
|
|
924
1239
|
query = query + " " + suffix;
|
|
@@ -926,10 +1241,8 @@ function buildUpdateQuery(options, id, suffix) {
|
|
|
926
1241
|
return [query, values, logValues];
|
|
927
1242
|
}
|
|
928
1243
|
exports.buildUpdateQuery = buildUpdateQuery;
|
|
929
|
-
async function editRow(queryer, options,
|
|
930
|
-
const [query, values, logValues] = buildUpdateQuery(options,
|
|
931
|
-
// add id as value to prepared query
|
|
932
|
-
values.push(id);
|
|
1244
|
+
async function editRow(queryer, options, suffix) {
|
|
1245
|
+
const [query, values, logValues] = buildUpdateQuery(options, suffix);
|
|
933
1246
|
const res = await mutateRow(queryer, query, values, logValues, options);
|
|
934
1247
|
if (res?.rowCount == 1) {
|
|
935
1248
|
// for now assume id primary key
|
|
@@ -940,10 +1253,8 @@ async function editRow(queryer, options, id, suffix) {
|
|
|
940
1253
|
return null;
|
|
941
1254
|
}
|
|
942
1255
|
exports.editRow = editRow;
|
|
943
|
-
function editRowSync(queryer, options,
|
|
944
|
-
const [query, values, logValues] = buildUpdateQuery(options,
|
|
945
|
-
// add id as value to prepared query
|
|
946
|
-
values.push(id);
|
|
1256
|
+
function editRowSync(queryer, options, suffix) {
|
|
1257
|
+
const [query, values, logValues] = buildUpdateQuery(options, suffix);
|
|
947
1258
|
const res = mutateRowSync(queryer, query, values, logValues, options);
|
|
948
1259
|
if (res?.rowCount == 1) {
|
|
949
1260
|
// for now assume id primary key
|
|
@@ -994,6 +1305,12 @@ class AssocEdge {
|
|
|
994
1305
|
this.edgeType = data.edge_type;
|
|
995
1306
|
this.time = data.time;
|
|
996
1307
|
this.data = data.data;
|
|
1308
|
+
this.rawData = data;
|
|
1309
|
+
}
|
|
1310
|
+
__getRawData() {
|
|
1311
|
+
// incase there's extra db fields. useful for tests
|
|
1312
|
+
// in production, a subclass of this should be in use so we won't need this...
|
|
1313
|
+
return this.rawData;
|
|
997
1314
|
}
|
|
998
1315
|
getCursor() {
|
|
999
1316
|
return getCursor({
|
|
@@ -1100,6 +1417,21 @@ async function loadEdges(options) {
|
|
|
1100
1417
|
return loadCustomEdges({ ...options, ctr: AssocEdge });
|
|
1101
1418
|
}
|
|
1102
1419
|
exports.loadEdges = loadEdges;
|
|
1420
|
+
function getEdgeClauseAndFields(cls, options) {
|
|
1421
|
+
let fields = edgeFields;
|
|
1422
|
+
if (globalSchema?.transformEdgeRead) {
|
|
1423
|
+
const transformClause = globalSchema.transformEdgeRead();
|
|
1424
|
+
if (!options.disableTransformations) {
|
|
1425
|
+
cls = clause.And(cls, transformClause);
|
|
1426
|
+
}
|
|
1427
|
+
fields = edgeFields.concat(transformClause.columns());
|
|
1428
|
+
}
|
|
1429
|
+
return {
|
|
1430
|
+
cls,
|
|
1431
|
+
fields,
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
exports.getEdgeClauseAndFields = getEdgeClauseAndFields;
|
|
1103
1435
|
async function loadCustomEdges(options) {
|
|
1104
1436
|
const { id1, edgeType, context } = options;
|
|
1105
1437
|
const edgeData = await loadEdgeData(edgeType);
|
|
@@ -1111,10 +1443,11 @@ async function loadCustomEdges(options) {
|
|
|
1111
1443
|
if (options.queryOptions?.clause) {
|
|
1112
1444
|
cls = clause.And(cls, options.queryOptions.clause);
|
|
1113
1445
|
}
|
|
1446
|
+
const { cls: actualClause, fields } = getEdgeClauseAndFields(cls, options);
|
|
1114
1447
|
const rows = await loadRows({
|
|
1115
1448
|
tableName: edgeData.edgeTable,
|
|
1116
|
-
fields:
|
|
1117
|
-
clause:
|
|
1449
|
+
fields: fields,
|
|
1450
|
+
clause: actualClause,
|
|
1118
1451
|
orderby: options.queryOptions?.orderby || defaultOptions.orderby,
|
|
1119
1452
|
limit: options.queryOptions?.limit || defaultOptions.limit,
|
|
1120
1453
|
context,
|
|
@@ -1130,10 +1463,11 @@ async function loadUniqueEdge(options) {
|
|
|
1130
1463
|
if (!edgeData) {
|
|
1131
1464
|
throw new Error(`error loading edge data for ${edgeType}`);
|
|
1132
1465
|
}
|
|
1466
|
+
const { cls, fields } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
|
|
1133
1467
|
const row = await loadRow({
|
|
1134
1468
|
tableName: edgeData.edgeTable,
|
|
1135
|
-
fields:
|
|
1136
|
-
clause:
|
|
1469
|
+
fields: fields,
|
|
1470
|
+
clause: cls,
|
|
1137
1471
|
context,
|
|
1138
1472
|
});
|
|
1139
1473
|
if (!row) {
|
|
@@ -1160,11 +1494,12 @@ async function loadRawEdgeCountX(options) {
|
|
|
1160
1494
|
if (!edgeData) {
|
|
1161
1495
|
throw new Error(`error loading edge data for ${edgeType}`);
|
|
1162
1496
|
}
|
|
1497
|
+
const { cls } = getEdgeClauseAndFields(clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)), options);
|
|
1163
1498
|
const row = await loadRowX({
|
|
1164
1499
|
tableName: edgeData.edgeTable,
|
|
1165
1500
|
// sqlite needs as count otherwise it returns count(1)
|
|
1166
1501
|
fields: ["count(1) as count"],
|
|
1167
|
-
clause:
|
|
1502
|
+
clause: cls,
|
|
1168
1503
|
context,
|
|
1169
1504
|
});
|
|
1170
1505
|
return parseInt(row["count"], 10) || 0;
|
|
@@ -1190,19 +1525,20 @@ async function loadNodesByEdge(viewer, id1, edgeType, options) {
|
|
|
1190
1525
|
}
|
|
1191
1526
|
exports.loadNodesByEdge = loadNodesByEdge;
|
|
1192
1527
|
async function applyPrivacyPolicyForRow(viewer, options, row) {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
}
|
|
1196
|
-
const ent = new options.ent(viewer, row);
|
|
1197
|
-
return await applyPrivacyPolicyForEnt(viewer, ent, row, options);
|
|
1528
|
+
const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
|
|
1529
|
+
return r instanceof Error ? null : r;
|
|
1198
1530
|
}
|
|
1199
1531
|
exports.applyPrivacyPolicyForRow = applyPrivacyPolicyForRow;
|
|
1532
|
+
async function applyPrivacyPolicyForRowImpl(viewer, options, row) {
|
|
1533
|
+
const ent = new options.ent(viewer, row);
|
|
1534
|
+
return applyPrivacyPolicyForEnt(viewer, ent, row, options);
|
|
1535
|
+
}
|
|
1200
1536
|
async function applyPrivacyPolicyForRowX(viewer, options, row) {
|
|
1201
1537
|
const ent = new options.ent(viewer, row);
|
|
1202
1538
|
return await applyPrivacyPolicyForEntX(viewer, ent, row, options);
|
|
1203
1539
|
}
|
|
1204
|
-
|
|
1205
|
-
async function
|
|
1540
|
+
// deprecated. doesn't use entcache
|
|
1541
|
+
async function applyPrivacyPolicyForRowsDeprecated(viewer, rows, options) {
|
|
1206
1542
|
let m = new Map();
|
|
1207
1543
|
// apply privacy logic
|
|
1208
1544
|
await Promise.all(rows.map(async (row) => {
|
|
@@ -1213,6 +1549,22 @@ async function applyPrivacyPolicyForRows(viewer, rows, options) {
|
|
|
1213
1549
|
}));
|
|
1214
1550
|
return m;
|
|
1215
1551
|
}
|
|
1552
|
+
async function applyPrivacyPolicyForRows(viewer, rows, options) {
|
|
1553
|
+
const result = new Array(rows.length);
|
|
1554
|
+
if (!rows.length) {
|
|
1555
|
+
return [];
|
|
1556
|
+
}
|
|
1557
|
+
const entLoader = getEntLoader(viewer, options);
|
|
1558
|
+
await Promise.all(rows.map(async (row, idx) => {
|
|
1559
|
+
const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options, entLoader);
|
|
1560
|
+
if (r instanceof ErrorWrapper) {
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
result[idx] = r;
|
|
1564
|
+
}));
|
|
1565
|
+
// filter ents that aren't visible because of privacy
|
|
1566
|
+
return result.filter((r) => r !== undefined);
|
|
1567
|
+
}
|
|
1216
1568
|
exports.applyPrivacyPolicyForRows = applyPrivacyPolicyForRows;
|
|
1217
1569
|
async function loadEdgeWithConst(viewer, id1, id2, edgeEnum, edgeType) {
|
|
1218
1570
|
const edge = await loadEdgeForID2({
|