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