@snowtop/ent 0.0.36 → 0.0.39-alpha4
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 +2 -0
- package/action/orchestrator.d.ts +6 -0
- package/action/orchestrator.js +98 -20
- package/core/base.d.ts +1 -0
- package/core/clause.d.ts +5 -3
- package/core/clause.js +67 -4
- package/core/config.d.ts +11 -0
- package/core/config.js +12 -0
- package/core/ent.d.ts +2 -2
- package/core/ent.js +61 -6
- package/core/loaders/assoc_edge_loader.js +0 -1
- package/core/loaders/object_loader.d.ts +1 -0
- package/core/loaders/object_loader.js +22 -4
- package/graphql/graphql.d.ts +3 -2
- package/graphql/graphql.js +22 -21
- package/graphql/node_resolver.d.ts +0 -1
- package/package.json +2 -2
- package/parse_schema/parse.d.ts +7 -1
- package/parse_schema/parse.js +40 -1
- package/schema/index.d.ts +1 -1
- package/schema/index.js +3 -1
- package/schema/schema.d.ts +28 -1
- package/schema/schema.js +49 -5
- package/scripts/custom_graphql.js +122 -15
- package/testutils/builder.js +5 -0
- package/testutils/ent-graphql-tests/index.d.ts +2 -0
- package/testutils/ent-graphql-tests/index.js +7 -5
package/action/action.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Ent, EntConstructor, Viewer, ID, Data, PrivacyPolicy, Context } from "../core/base";
|
|
2
2
|
import { DataOperation, AssocEdgeInputOptions } from "../core/ent";
|
|
3
3
|
import { Queryer } from "../core/db";
|
|
4
|
+
import { TransformedUpdateOperation, UpdateOperation } from "../schema";
|
|
4
5
|
export declare enum WriteOperation {
|
|
5
6
|
Insert = "insert",
|
|
6
7
|
Edit = "edit",
|
|
@@ -50,6 +51,7 @@ export interface Action<T extends Ent> {
|
|
|
50
51
|
observers?: Observer<T>[];
|
|
51
52
|
validators?: Validator<T>[];
|
|
52
53
|
getInput(): Data;
|
|
54
|
+
transformWrite?: (stmt: UpdateOperation<T>) => Promise<TransformedUpdateOperation<T>> | TransformedUpdateOperation<T> | undefined;
|
|
53
55
|
valid(): Promise<boolean>;
|
|
54
56
|
validX(): Promise<void>;
|
|
55
57
|
viewerForEntLoad?(data: Data): Viewer | Promise<Viewer>;
|
package/action/orchestrator.d.ts
CHANGED
|
@@ -41,8 +41,12 @@ export declare class Orchestrator<T extends Ent> {
|
|
|
41
41
|
viewer: Viewer;
|
|
42
42
|
private defaultFieldsByFieldName;
|
|
43
43
|
private defaultFieldsByTSName;
|
|
44
|
+
private actualOperation;
|
|
45
|
+
private existingEnt?;
|
|
46
|
+
private disableTransformations;
|
|
44
47
|
constructor(options: OrchestratorOptions<T, Data>);
|
|
45
48
|
private addEdge;
|
|
49
|
+
setDisableTransformations(val: boolean): void;
|
|
46
50
|
addInboundEdge<T2 extends Ent>(id1: ID | Builder<T2>, edgeType: string, nodeType: string, options?: AssocEdgeInputOptions): void;
|
|
47
51
|
addOutboundEdge<T2 extends Ent>(id2: ID | Builder<T2>, edgeType: string, nodeType: string, options?: AssocEdgeInputOptions): void;
|
|
48
52
|
removeInboundEdge(id1: ID, edgeType: string): void;
|
|
@@ -54,6 +58,8 @@ export declare class Orchestrator<T extends Ent> {
|
|
|
54
58
|
private buildEdgeOps;
|
|
55
59
|
private throwError;
|
|
56
60
|
private getEntForPrivacyPolicy;
|
|
61
|
+
private getSQLStatementOperation;
|
|
62
|
+
private getWriteOpForSQLStamentOp;
|
|
57
63
|
private validate;
|
|
58
64
|
private triggers;
|
|
59
65
|
private validators;
|
package/action/orchestrator.js
CHANGED
|
@@ -62,6 +62,8 @@ class Orchestrator {
|
|
|
62
62
|
this.defaultFieldsByFieldName = {};
|
|
63
63
|
this.defaultFieldsByTSName = {};
|
|
64
64
|
this.viewer = options.viewer;
|
|
65
|
+
this.actualOperation = this.options.operation;
|
|
66
|
+
this.existingEnt = this.options.builder.existingEnt;
|
|
65
67
|
}
|
|
66
68
|
addEdge(edge, op) {
|
|
67
69
|
this.edgeSet.add(edge.edgeType);
|
|
@@ -80,6 +82,9 @@ class Orchestrator {
|
|
|
80
82
|
m1.set(op, m2);
|
|
81
83
|
this.edges.set(edge.edgeType, m1);
|
|
82
84
|
}
|
|
85
|
+
setDisableTransformations(val) {
|
|
86
|
+
this.disableTransformations = val;
|
|
87
|
+
}
|
|
83
88
|
addInboundEdge(id1, edgeType, nodeType, options) {
|
|
84
89
|
this.addEdge(new edgeInputData({
|
|
85
90
|
id: id1,
|
|
@@ -135,24 +140,27 @@ class Orchestrator {
|
|
|
135
140
|
}
|
|
136
141
|
buildMainOp() {
|
|
137
142
|
// this assumes we have validated fields
|
|
138
|
-
switch (this.
|
|
143
|
+
switch (this.actualOperation) {
|
|
139
144
|
case action_1.WriteOperation.Delete:
|
|
140
|
-
return new ent_1.DeleteNodeOperation(this.
|
|
145
|
+
return new ent_1.DeleteNodeOperation(this.existingEnt.id, {
|
|
141
146
|
tableName: this.options.tableName,
|
|
142
147
|
});
|
|
143
148
|
default:
|
|
149
|
+
if (this.actualOperation === action_1.WriteOperation.Edit && !this.existingEnt) {
|
|
150
|
+
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
151
|
+
}
|
|
144
152
|
const opts = {
|
|
145
153
|
fields: this.validatedFields,
|
|
146
154
|
tableName: this.options.tableName,
|
|
147
155
|
fieldsToResolve: this.fieldsToResolve,
|
|
148
156
|
key: this.options.key,
|
|
149
|
-
|
|
157
|
+
loadEntOptions: this.options.loaderOptions,
|
|
150
158
|
placeholderID: this.options.builder.placeholderID,
|
|
151
159
|
};
|
|
152
160
|
if (this.logValues) {
|
|
153
161
|
opts.fieldsToLog = this.logValues;
|
|
154
162
|
}
|
|
155
|
-
this.mainOp = new ent_1.EditNodeOperation(opts, this.
|
|
163
|
+
this.mainOp = new ent_1.EditNodeOperation(opts, this.existingEnt);
|
|
156
164
|
return this.mainOp;
|
|
157
165
|
}
|
|
158
166
|
}
|
|
@@ -219,35 +227,57 @@ class Orchestrator {
|
|
|
219
227
|
if (!privacyPolicy || !action) {
|
|
220
228
|
throw new Error(`shouldn't get here if no privacyPolicy for action`);
|
|
221
229
|
}
|
|
222
|
-
if (this.
|
|
230
|
+
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
223
231
|
return new EntCannotCreateEntError(privacyPolicy, action);
|
|
224
232
|
}
|
|
225
|
-
else if (this.
|
|
226
|
-
return new EntCannotEditEntError(privacyPolicy, action, this.
|
|
233
|
+
else if (this.actualOperation === action_1.WriteOperation.Edit) {
|
|
234
|
+
return new EntCannotEditEntError(privacyPolicy, action, this.existingEnt);
|
|
227
235
|
}
|
|
228
|
-
return new EntCannotDeleteEntError(privacyPolicy, action, this.
|
|
236
|
+
return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
|
|
229
237
|
}
|
|
230
238
|
getEntForPrivacyPolicy(editedData) {
|
|
231
|
-
if (this.
|
|
232
|
-
return this.
|
|
239
|
+
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
240
|
+
return this.existingEnt;
|
|
233
241
|
}
|
|
234
242
|
// we create an unsafe ent to be used for privacy policies
|
|
235
243
|
return new this.options.builder.ent(this.options.builder.viewer, editedData);
|
|
236
244
|
}
|
|
245
|
+
getSQLStatementOperation() {
|
|
246
|
+
switch (this.actualOperation) {
|
|
247
|
+
case action_1.WriteOperation.Edit:
|
|
248
|
+
return schema_1.SQLStatementOperation.Update;
|
|
249
|
+
case action_1.WriteOperation.Insert:
|
|
250
|
+
return schema_1.SQLStatementOperation.Insert;
|
|
251
|
+
case action_1.WriteOperation.Delete:
|
|
252
|
+
return schema_1.SQLStatementOperation.Delete;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
getWriteOpForSQLStamentOp(op) {
|
|
256
|
+
switch (op) {
|
|
257
|
+
case schema_1.SQLStatementOperation.Update:
|
|
258
|
+
return action_1.WriteOperation.Edit;
|
|
259
|
+
case schema_1.SQLStatementOperation.Insert:
|
|
260
|
+
return action_1.WriteOperation.Insert;
|
|
261
|
+
case schema_1.SQLStatementOperation.Update:
|
|
262
|
+
return action_1.WriteOperation.Delete;
|
|
263
|
+
default:
|
|
264
|
+
throw new Error("invalid path");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
237
267
|
async validate() {
|
|
238
268
|
// existing ent required for edit or delete operations
|
|
239
|
-
switch (this.
|
|
269
|
+
switch (this.actualOperation) {
|
|
240
270
|
case action_1.WriteOperation.Delete:
|
|
241
271
|
case action_1.WriteOperation.Edit:
|
|
242
|
-
if (!this.
|
|
243
|
-
throw new Error(
|
|
272
|
+
if (!this.existingEnt) {
|
|
273
|
+
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
244
274
|
}
|
|
245
275
|
}
|
|
246
276
|
const action = this.options.action;
|
|
247
277
|
const builder = this.options.builder;
|
|
248
278
|
// future optimization: can get schemaFields to memoize based on different values
|
|
249
279
|
const schemaFields = (0, schema_1.getFields)(this.options.schema);
|
|
250
|
-
let editedData = this.getFieldsWithDefaultValues(builder, schemaFields, action);
|
|
280
|
+
let editedData = await this.getFieldsWithDefaultValues(builder, schemaFields, action);
|
|
251
281
|
// this runs in following phases:
|
|
252
282
|
// * set default fields and pass to builder so the value can be checked by triggers/observers/validators
|
|
253
283
|
// * privacy policy (use unsafe ent if we have it)
|
|
@@ -300,18 +330,66 @@ class Orchestrator {
|
|
|
300
330
|
isBuilder(val) {
|
|
301
331
|
return val.placeholderID !== undefined;
|
|
302
332
|
}
|
|
303
|
-
getFieldsWithDefaultValues(builder, schemaFields, action) {
|
|
333
|
+
async getFieldsWithDefaultValues(builder, schemaFields, action) {
|
|
304
334
|
const editedFields = this.options.editedFields();
|
|
305
335
|
let data = {};
|
|
306
336
|
let defaultData = {};
|
|
307
337
|
let input = action?.getInput() || {};
|
|
308
338
|
let updateInput = false;
|
|
339
|
+
// transformations
|
|
340
|
+
// if action transformations. always do it
|
|
341
|
+
// if disable transformations set, don't do schema transform and just do the right thing
|
|
342
|
+
// else apply schema tranformation if it exists
|
|
343
|
+
let transformed;
|
|
344
|
+
if (action?.transformWrite) {
|
|
345
|
+
transformed = await action.transformWrite({
|
|
346
|
+
viewer: builder.viewer,
|
|
347
|
+
op: this.getSQLStatementOperation(),
|
|
348
|
+
data: editedFields,
|
|
349
|
+
existingEnt: this.existingEnt,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
else if (!this.disableTransformations) {
|
|
353
|
+
transformed = (0, schema_1.getTransformedUpdateOp)(this.options.schema, {
|
|
354
|
+
viewer: builder.viewer,
|
|
355
|
+
op: this.getSQLStatementOperation(),
|
|
356
|
+
data: editedFields,
|
|
357
|
+
existingEnt: this.existingEnt,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
if (transformed) {
|
|
361
|
+
if (transformed.data) {
|
|
362
|
+
updateInput = true;
|
|
363
|
+
for (const k in transformed.data) {
|
|
364
|
+
let field = schemaFields.get(k);
|
|
365
|
+
if (!field) {
|
|
366
|
+
throw new Error(`tried to transform field with unknown field ${k}`);
|
|
367
|
+
}
|
|
368
|
+
let val = transformed.data[k];
|
|
369
|
+
if (field.format) {
|
|
370
|
+
val = field.format(transformed.data[k]);
|
|
371
|
+
}
|
|
372
|
+
let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(field.name);
|
|
373
|
+
data[dbKey] = val;
|
|
374
|
+
this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(k)] = val;
|
|
375
|
+
// hmm do we need this?
|
|
376
|
+
// TODO how to do this for local tests?
|
|
377
|
+
// this.defaultFieldsByFieldName[k] = val;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
|
|
381
|
+
if (transformed.existingEnt) {
|
|
382
|
+
this.existingEnt = transformed.existingEnt;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// transforming before doing default fields so that we don't create a new id
|
|
386
|
+
// and anything that depends on the type of operations knows what it is
|
|
309
387
|
for (const [fieldName, field] of schemaFields) {
|
|
310
388
|
let value = editedFields.get(fieldName);
|
|
311
389
|
let defaultValue = undefined;
|
|
312
390
|
let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(field.name);
|
|
313
391
|
if (value === undefined) {
|
|
314
|
-
if (this.
|
|
392
|
+
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
315
393
|
if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
|
|
316
394
|
throw new Error(`cannot set both defaultToViewerOnCreate and defaultValueOnCreate`);
|
|
317
395
|
}
|
|
@@ -326,7 +404,7 @@ class Orchestrator {
|
|
|
326
404
|
}
|
|
327
405
|
}
|
|
328
406
|
if (field.defaultValueOnEdit &&
|
|
329
|
-
this.
|
|
407
|
+
this.actualOperation === action_1.WriteOperation.Edit) {
|
|
330
408
|
defaultValue = field.defaultValueOnEdit(builder, input);
|
|
331
409
|
}
|
|
332
410
|
}
|
|
@@ -374,7 +452,7 @@ class Orchestrator {
|
|
|
374
452
|
// not setting server default as we're depending on the database handling that.
|
|
375
453
|
// server default allowed
|
|
376
454
|
field.serverDefault === undefined &&
|
|
377
|
-
this.
|
|
455
|
+
this.actualOperation === action_1.WriteOperation.Insert) {
|
|
378
456
|
throw new Error(`required field ${field.name} not set`);
|
|
379
457
|
}
|
|
380
458
|
}
|
|
@@ -406,7 +484,7 @@ class Orchestrator {
|
|
|
406
484
|
return value;
|
|
407
485
|
}
|
|
408
486
|
async formatAndValidateFields(schemaFields) {
|
|
409
|
-
const op = this.
|
|
487
|
+
const op = this.actualOperation;
|
|
410
488
|
if (op === action_1.WriteOperation.Delete) {
|
|
411
489
|
return;
|
|
412
490
|
}
|
|
@@ -494,7 +572,7 @@ class Orchestrator {
|
|
|
494
572
|
const viewer = await this.viewerForEntLoad(row);
|
|
495
573
|
const ent = await (0, ent_1.applyPrivacyPolicyForRow)(viewer, this.options.loaderOptions, row);
|
|
496
574
|
if (!ent) {
|
|
497
|
-
if (this.
|
|
575
|
+
if (this.actualOperation == action_1.WriteOperation.Insert) {
|
|
498
576
|
throw new Error(`was able to create ent but not load it`);
|
|
499
577
|
}
|
|
500
578
|
else {
|
package/core/base.d.ts
CHANGED
|
@@ -62,6 +62,7 @@ export interface SelectBaseDataOptions extends DataOptions {
|
|
|
62
62
|
}
|
|
63
63
|
export interface SelectDataOptions extends SelectBaseDataOptions {
|
|
64
64
|
key: string;
|
|
65
|
+
clause?: clause.Clause;
|
|
65
66
|
}
|
|
66
67
|
export interface QueryableDataOptions extends SelectBaseDataOptions, QueryDataOptions {
|
|
67
68
|
}
|
package/core/clause.d.ts
CHANGED
|
@@ -12,8 +12,10 @@ declare class simpleClause implements Clause {
|
|
|
12
12
|
protected col: string;
|
|
13
13
|
private value;
|
|
14
14
|
private op;
|
|
15
|
-
|
|
15
|
+
private handleSqliteNull?;
|
|
16
|
+
constructor(col: string, value: any, op: string, handleSqliteNull?: Clause | undefined);
|
|
16
17
|
clause(idx: number): string;
|
|
18
|
+
private sqliteNull;
|
|
17
19
|
values(): any[];
|
|
18
20
|
logValues(): any[];
|
|
19
21
|
instanceKey(): string;
|
|
@@ -27,8 +29,8 @@ declare class compositeClause implements Clause {
|
|
|
27
29
|
logValues(): any[];
|
|
28
30
|
instanceKey(): string;
|
|
29
31
|
}
|
|
30
|
-
export declare function Eq(col: string, value: any):
|
|
31
|
-
export declare function NotEq(col: string, value: any):
|
|
32
|
+
export declare function Eq(col: string, value: any): Clause;
|
|
33
|
+
export declare function NotEq(col: string, value: any): Clause;
|
|
32
34
|
export declare function Greater(col: string, value: any): simpleClause;
|
|
33
35
|
export declare function Less(col: string, value: any): simpleClause;
|
|
34
36
|
export declare function GreaterEq(col: string, value: any): simpleClause;
|
package/core/clause.js
CHANGED
|
@@ -22,7 +22,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
exports.sensitiveValue = exports.In = exports.Or = exports.And = exports.LessEq = exports.GreaterEq = exports.Less = exports.Greater = exports.NotEq = exports.Eq = void 0;
|
|
23
23
|
const db_1 = __importStar(require("./db"));
|
|
24
24
|
function isSensitive(val) {
|
|
25
|
-
return (
|
|
25
|
+
return (val !== null &&
|
|
26
|
+
typeof val === "object" &&
|
|
27
|
+
val.logValue !== undefined);
|
|
26
28
|
}
|
|
27
29
|
function rawValue(val) {
|
|
28
30
|
if (isSensitive(val)) {
|
|
@@ -31,33 +33,94 @@ function rawValue(val) {
|
|
|
31
33
|
return val;
|
|
32
34
|
}
|
|
33
35
|
class simpleClause {
|
|
34
|
-
constructor(col, value, op) {
|
|
36
|
+
constructor(col, value, op, handleSqliteNull) {
|
|
35
37
|
this.col = col;
|
|
36
38
|
this.value = value;
|
|
37
39
|
this.op = op;
|
|
40
|
+
this.handleSqliteNull = handleSqliteNull;
|
|
38
41
|
}
|
|
39
42
|
clause(idx) {
|
|
43
|
+
const sqliteClause = this.sqliteNull();
|
|
44
|
+
if (sqliteClause) {
|
|
45
|
+
return sqliteClause.clause(idx);
|
|
46
|
+
}
|
|
40
47
|
if (db_1.default.getDialect() === db_1.Dialect.Postgres) {
|
|
41
48
|
return `${this.col} ${this.op} $${idx}`;
|
|
42
49
|
}
|
|
43
50
|
return `${this.col} ${this.op} ?`;
|
|
44
51
|
}
|
|
52
|
+
sqliteNull() {
|
|
53
|
+
if (!this.handleSqliteNull) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const dialect = db_1.default.getDialect();
|
|
57
|
+
if (this.value !== null || dialect !== db_1.Dialect.SQLite) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
return this.handleSqliteNull;
|
|
61
|
+
}
|
|
45
62
|
values() {
|
|
63
|
+
const sqliteClause = this.sqliteNull();
|
|
64
|
+
if (sqliteClause) {
|
|
65
|
+
return sqliteClause.values();
|
|
66
|
+
}
|
|
46
67
|
if (isSensitive(this.value)) {
|
|
47
68
|
return [this.value.value()];
|
|
48
69
|
}
|
|
49
70
|
return [this.value];
|
|
50
71
|
}
|
|
51
72
|
logValues() {
|
|
73
|
+
const sqliteClause = this.sqliteNull();
|
|
74
|
+
if (sqliteClause) {
|
|
75
|
+
return sqliteClause.logValues();
|
|
76
|
+
}
|
|
52
77
|
if (isSensitive(this.value)) {
|
|
53
78
|
return [this.value.logValue()];
|
|
54
79
|
}
|
|
55
80
|
return [this.value];
|
|
56
81
|
}
|
|
57
82
|
instanceKey() {
|
|
83
|
+
const sqliteClause = this.sqliteNull();
|
|
84
|
+
if (sqliteClause) {
|
|
85
|
+
return sqliteClause.instanceKey();
|
|
86
|
+
}
|
|
58
87
|
return `${this.col}${this.op}${rawValue(this.value)}`;
|
|
59
88
|
}
|
|
60
89
|
}
|
|
90
|
+
class isNullClause {
|
|
91
|
+
constructor(col) {
|
|
92
|
+
this.col = col;
|
|
93
|
+
}
|
|
94
|
+
clause(idx) {
|
|
95
|
+
return `${this.col} IS NULL`;
|
|
96
|
+
}
|
|
97
|
+
values() {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
logValues() {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
instanceKey() {
|
|
104
|
+
return `${this.col} IS NULL`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
class isNotNullClause {
|
|
108
|
+
constructor(col) {
|
|
109
|
+
this.col = col;
|
|
110
|
+
}
|
|
111
|
+
clause(idx) {
|
|
112
|
+
return `${this.col} IS NOT NULL`;
|
|
113
|
+
}
|
|
114
|
+
values() {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
logValues() {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
instanceKey() {
|
|
121
|
+
return `${this.col} IS NOT NULL`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
61
124
|
class inClause {
|
|
62
125
|
constructor(col, value) {
|
|
63
126
|
this.col = col;
|
|
@@ -146,11 +209,11 @@ class compositeClause {
|
|
|
146
209
|
}
|
|
147
210
|
}
|
|
148
211
|
function Eq(col, value) {
|
|
149
|
-
return new simpleClause(col, value, "=");
|
|
212
|
+
return new simpleClause(col, value, "=", new isNullClause(col));
|
|
150
213
|
}
|
|
151
214
|
exports.Eq = Eq;
|
|
152
215
|
function NotEq(col, value) {
|
|
153
|
-
return new simpleClause(col, value, "!=");
|
|
216
|
+
return new simpleClause(col, value, "!=", new isNotNullClause(col));
|
|
154
217
|
}
|
|
155
218
|
exports.NotEq = NotEq;
|
|
156
219
|
function Greater(col, value) {
|
package/core/config.d.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { Database, DBDict } from "./db";
|
|
3
3
|
declare type logType = "query" | "warn" | "info" | "error" | "debug";
|
|
4
|
+
declare enum graphqlMutationName {
|
|
5
|
+
NOUN_VERB = "NounVerb",
|
|
6
|
+
VERB_NOUN = "VerbNoun"
|
|
7
|
+
}
|
|
8
|
+
declare enum graphQLFieldFormat {
|
|
9
|
+
LOWER_CAMEL = "lowerCamel",
|
|
10
|
+
SNAKE_CASE = "snake_case"
|
|
11
|
+
}
|
|
4
12
|
export interface Config {
|
|
5
13
|
dbConnectionString?: string;
|
|
6
14
|
dbFile?: string;
|
|
7
15
|
db?: Database | DBDict;
|
|
8
16
|
log?: logType | logType[];
|
|
9
17
|
codegen?: CodegenConfig;
|
|
18
|
+
customGraphQLJSONPath?: string;
|
|
10
19
|
}
|
|
11
20
|
interface CodegenConfig {
|
|
12
21
|
defaultEntPolicy?: PrivacyConfig;
|
|
@@ -17,6 +26,8 @@ interface CodegenConfig {
|
|
|
17
26
|
generatedHeader?: string;
|
|
18
27
|
disableBase64Encoding?: boolean;
|
|
19
28
|
generateRootResolvers?: boolean;
|
|
29
|
+
defaultGraphQLMutationName?: graphqlMutationName;
|
|
30
|
+
defaultGraphQLFieldFormat?: graphQLFieldFormat;
|
|
20
31
|
}
|
|
21
32
|
interface PrettierConfig {
|
|
22
33
|
custom?: boolean;
|
package/core/config.js
CHANGED
|
@@ -28,6 +28,18 @@ const js_yaml_1 = require("js-yaml");
|
|
|
28
28
|
const db_1 = __importDefault(require("./db"));
|
|
29
29
|
const path = __importStar(require("path"));
|
|
30
30
|
const logger_1 = require("./logger");
|
|
31
|
+
// ent.config.ts eventually. for now ent.yml
|
|
32
|
+
// or ent.yml?
|
|
33
|
+
var graphqlMutationName;
|
|
34
|
+
(function (graphqlMutationName) {
|
|
35
|
+
graphqlMutationName["NOUN_VERB"] = "NounVerb";
|
|
36
|
+
graphqlMutationName["VERB_NOUN"] = "VerbNoun";
|
|
37
|
+
})(graphqlMutationName || (graphqlMutationName = {}));
|
|
38
|
+
var graphQLFieldFormat;
|
|
39
|
+
(function (graphQLFieldFormat) {
|
|
40
|
+
graphQLFieldFormat["LOWER_CAMEL"] = "lowerCamel";
|
|
41
|
+
graphQLFieldFormat["SNAKE_CASE"] = "snake_case";
|
|
42
|
+
})(graphQLFieldFormat || (graphQLFieldFormat = {}));
|
|
31
43
|
function setConfig(cfg) {
|
|
32
44
|
if (cfg.log) {
|
|
33
45
|
(0, logger_1.setLogLevels)(cfg.log);
|
package/core/ent.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Queryer, SyncQueryer } from "./db";
|
|
2
|
-
import { Viewer, Ent, ID, LoadRowsOptions, LoadRowOptions, Data, DataOptions, QueryableDataOptions, EditRowOptions, LoadEntOptions, LoadCustomEntOptions, EdgeQueryableDataOptions, Context, SelectBaseDataOptions, CreateRowOptions, QueryDataOptions
|
|
2
|
+
import { Viewer, Ent, ID, LoadRowsOptions, LoadRowOptions, Data, DataOptions, QueryableDataOptions, EditRowOptions, LoadEntOptions, LoadCustomEntOptions, EdgeQueryableDataOptions, Context, SelectBaseDataOptions, CreateRowOptions, QueryDataOptions } from "./base";
|
|
3
3
|
import { Executor } from "../action/action";
|
|
4
4
|
import * as clause from "./clause";
|
|
5
5
|
import { Builder } from "../action";
|
|
@@ -51,7 +51,7 @@ export interface DataOperation<T extends Ent = Ent> {
|
|
|
51
51
|
}
|
|
52
52
|
export interface EditNodeOptions<T extends Ent> extends EditRowOptions {
|
|
53
53
|
fieldsToResolve: string[];
|
|
54
|
-
|
|
54
|
+
loadEntOptions: LoadEntOptions<T>;
|
|
55
55
|
placeholderID?: ID;
|
|
56
56
|
}
|
|
57
57
|
export declare class EditNodeOperation<T extends Ent> implements DataOperation {
|
package/core/ent.js
CHANGED
|
@@ -84,8 +84,54 @@ function createDataLoader(options) {
|
|
|
84
84
|
return result;
|
|
85
85
|
}, loaderOptions);
|
|
86
86
|
}
|
|
87
|
+
// TODO probably delete since we're going to generate it now
|
|
88
|
+
// instead of passing this here.
|
|
89
|
+
// async function doPerformQuery<T extends Ent>(
|
|
90
|
+
// viewer: Viewer,
|
|
91
|
+
// id: ID,
|
|
92
|
+
// options: LoadEntOptions<T>,
|
|
93
|
+
// ): Promise<Data | null> {
|
|
94
|
+
// let newClause: clause.Clause | undefined = undefined;
|
|
95
|
+
// if (options.schema) {
|
|
96
|
+
// const schema = getSchema(options.schema);
|
|
97
|
+
// if (schema.patterns) {
|
|
98
|
+
// for (const p of schema.patterns) {
|
|
99
|
+
// if (p.transformRead) {
|
|
100
|
+
// newClause = p.transformRead({
|
|
101
|
+
// op: QueryOperation.Select,
|
|
102
|
+
// clause: clause.Eq("id", id),
|
|
103
|
+
// });
|
|
104
|
+
// }
|
|
105
|
+
// }
|
|
106
|
+
// }
|
|
107
|
+
// }
|
|
108
|
+
// // we want a loader if possible . we don't want this to slow
|
|
109
|
+
// // things down...
|
|
110
|
+
// // id = x and deleted_at == null
|
|
111
|
+
// if (newClause) {
|
|
112
|
+
// // loader
|
|
113
|
+
// // return loadRow({});
|
|
114
|
+
// // TODO
|
|
115
|
+
// }
|
|
116
|
+
// // we want a deleted_at loader...
|
|
117
|
+
// return options.loaderFactory.createLoader(viewer.context).load(id);
|
|
118
|
+
// }
|
|
87
119
|
// Ent accessors
|
|
88
120
|
async function loadEnt(viewer, id, options) {
|
|
121
|
+
let newClause;
|
|
122
|
+
// if (options.schema) {
|
|
123
|
+
// const schema = getSchema(options.schema);
|
|
124
|
+
// if (schema.patterns) {
|
|
125
|
+
// for (const p of schema.patterns) {
|
|
126
|
+
// if (p.transformRead) {
|
|
127
|
+
// newClause = p.transformRead({
|
|
128
|
+
// op: QueryOperation.Select,
|
|
129
|
+
// clause: clause.Eq("id", id),
|
|
130
|
+
// });
|
|
131
|
+
// }
|
|
132
|
+
// }
|
|
133
|
+
// }
|
|
134
|
+
// }
|
|
89
135
|
const row = await options.loaderFactory.createLoader(viewer.context).load(id);
|
|
90
136
|
return await applyPrivacyPolicyForRow(viewer, options, row);
|
|
91
137
|
}
|
|
@@ -434,6 +480,8 @@ class EditNodeOperation {
|
|
|
434
480
|
};
|
|
435
481
|
if (this.existingEnt) {
|
|
436
482
|
if (this.hasData(options.fields)) {
|
|
483
|
+
// even this with returning * doesn't work if transformed...
|
|
484
|
+
// we can have a transformed flag to see if it should be returned?
|
|
437
485
|
this.row = await editRow(queryer, options, this.existingEnt.id, "RETURNING *");
|
|
438
486
|
}
|
|
439
487
|
else {
|
|
@@ -445,20 +493,27 @@ class EditNodeOperation {
|
|
|
445
493
|
}
|
|
446
494
|
}
|
|
447
495
|
reloadRow(queryer, id, options) {
|
|
496
|
+
// TODO this isn'talways an ObjectLoader. should throw or figure out a way to get query
|
|
497
|
+
// and run this on its own...
|
|
498
|
+
const loader = this.options.loadEntOptions.loaderFactory.createLoader(options.context);
|
|
499
|
+
const opts = loader.getOptions();
|
|
500
|
+
let cls = clause.Eq(options.key, id);
|
|
501
|
+
if (opts.clause) {
|
|
502
|
+
cls = clause.And(opts.clause, cls);
|
|
503
|
+
}
|
|
448
504
|
const query = buildQuery({
|
|
449
|
-
fields: ["*"],
|
|
505
|
+
fields: opts.fields.length ? opts.fields : ["*"],
|
|
450
506
|
tableName: options.tableName,
|
|
451
|
-
clause:
|
|
507
|
+
clause: cls,
|
|
452
508
|
});
|
|
453
509
|
// special case log here because we're not going through any of the normal
|
|
454
510
|
// methods here because those are async and this is sync
|
|
455
511
|
// this is the only place we're doing this so only handling here
|
|
456
512
|
logQuery(query, [id]);
|
|
457
513
|
const r = queryer.querySync(query, [id]);
|
|
458
|
-
if (r.rows.length
|
|
459
|
-
|
|
514
|
+
if (r.rows.length === 1) {
|
|
515
|
+
this.row = r.rows[0];
|
|
460
516
|
}
|
|
461
|
-
this.row = r.rows[0];
|
|
462
517
|
}
|
|
463
518
|
performWriteSync(queryer, context) {
|
|
464
519
|
let options = {
|
|
@@ -487,7 +542,7 @@ class EditNodeOperation {
|
|
|
487
542
|
if (!this.row) {
|
|
488
543
|
return null;
|
|
489
544
|
}
|
|
490
|
-
return new this.options.ent(viewer, this.row);
|
|
545
|
+
return new this.options.loadEntOptions.ent(viewer, this.row);
|
|
491
546
|
}
|
|
492
547
|
}
|
|
493
548
|
exports.EditNodeOperation = EditNodeOperation;
|
|
@@ -162,7 +162,6 @@ class AssocEdgeLoaderFactory {
|
|
|
162
162
|
return this.createConfigurableLoader({}, context);
|
|
163
163
|
}
|
|
164
164
|
isConstructor(edgeCtr) {
|
|
165
|
-
// not constructor
|
|
166
165
|
return (edgeCtr.prototype &&
|
|
167
166
|
edgeCtr.prototype.constructor &&
|
|
168
167
|
edgeCtr.prototype.constructor.name.length > 0);
|
|
@@ -7,6 +7,7 @@ export declare class ObjectLoader<T> implements Loader<T, Data | null> {
|
|
|
7
7
|
private primedLoaders;
|
|
8
8
|
private memoizedInitPrime;
|
|
9
9
|
constructor(options: SelectDataOptions, context?: Context | undefined, toPrime?: ObjectLoaderFactory<T>[] | undefined);
|
|
10
|
+
getOptions(): SelectDataOptions;
|
|
10
11
|
private initPrime;
|
|
11
12
|
load(key: T): Promise<Data | null>;
|
|
12
13
|
clearAll(): void;
|
|
@@ -29,6 +29,9 @@ const clause = __importStar(require("../clause"));
|
|
|
29
29
|
const logger_1 = require("../logger");
|
|
30
30
|
const loader_1 = require("./loader");
|
|
31
31
|
const memoizee_1 = __importDefault(require("memoizee"));
|
|
32
|
+
// optional clause...
|
|
33
|
+
// so ObjectLoaderFactory and createDataLoader need to take a new optional field which is a clause that's always added here
|
|
34
|
+
// and we need a disableTransform which skips loader completely and uses loadRow...
|
|
32
35
|
function createDataLoader(options) {
|
|
33
36
|
const loaderOptions = {};
|
|
34
37
|
// if query logging is enabled, we should log what's happening with loader
|
|
@@ -40,9 +43,13 @@ function createDataLoader(options) {
|
|
|
40
43
|
return [];
|
|
41
44
|
}
|
|
42
45
|
let col = options.key;
|
|
46
|
+
let cls = clause.In(col, ...ids);
|
|
47
|
+
if (options.clause) {
|
|
48
|
+
cls = clause.And(options.clause, cls);
|
|
49
|
+
}
|
|
43
50
|
const rowOptions = {
|
|
44
51
|
...options,
|
|
45
|
-
clause:
|
|
52
|
+
clause: cls,
|
|
46
53
|
};
|
|
47
54
|
let m = new Map();
|
|
48
55
|
let result = [];
|
|
@@ -77,6 +84,9 @@ class ObjectLoader {
|
|
|
77
84
|
}
|
|
78
85
|
this.memoizedInitPrime = (0, memoizee_1.default)(this.initPrime.bind(this));
|
|
79
86
|
}
|
|
87
|
+
getOptions() {
|
|
88
|
+
return this.options;
|
|
89
|
+
}
|
|
80
90
|
initPrime() {
|
|
81
91
|
if (!this.context || !this.toPrime) {
|
|
82
92
|
return;
|
|
@@ -107,9 +117,13 @@ class ObjectLoader {
|
|
|
107
117
|
}
|
|
108
118
|
return result;
|
|
109
119
|
}
|
|
120
|
+
let cls = clause.Eq(this.options.key, key);
|
|
121
|
+
if (this.options.clause) {
|
|
122
|
+
cls = clause.And(this.options.clause, cls);
|
|
123
|
+
}
|
|
110
124
|
const rowOptions = {
|
|
111
125
|
...this.options,
|
|
112
|
-
clause:
|
|
126
|
+
clause: cls,
|
|
113
127
|
context: this.context,
|
|
114
128
|
};
|
|
115
129
|
return await (0, ent_1.loadRow)(rowOptions);
|
|
@@ -121,9 +135,13 @@ class ObjectLoader {
|
|
|
121
135
|
if (this.loader) {
|
|
122
136
|
return await this.loader.loadMany(keys);
|
|
123
137
|
}
|
|
138
|
+
let cls = clause.In(this.options.key, ...keys);
|
|
139
|
+
if (this.options.clause) {
|
|
140
|
+
cls = clause.And(this.options.clause, cls);
|
|
141
|
+
}
|
|
124
142
|
const rowOptions = {
|
|
125
143
|
...this.options,
|
|
126
|
-
clause:
|
|
144
|
+
clause: cls,
|
|
127
145
|
context: this.context,
|
|
128
146
|
};
|
|
129
147
|
return await (0, ent_1.loadRows)(rowOptions);
|
|
@@ -142,7 +160,7 @@ class ObjectLoaderFactory {
|
|
|
142
160
|
constructor(options) {
|
|
143
161
|
this.options = options;
|
|
144
162
|
this.toPrime = [];
|
|
145
|
-
this.name = `${options.tableName}:${options.key}`;
|
|
163
|
+
this.name = `${options.tableName}:${options.key}:${options.clause?.instanceKey() || ""}`;
|
|
146
164
|
}
|
|
147
165
|
createLoader(context) {
|
|
148
166
|
return (0, loader_1.getLoader)(this, () => {
|