@snowtop/ent 0.0.28 → 0.0.31
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 -3
- package/action/executor.d.ts +7 -12
- package/action/executor.js +35 -29
- package/action/experimental_action.d.ts +3 -1
- package/action/experimental_action.js +26 -1
- package/action/index.d.ts +1 -1
- package/action/orchestrator.d.ts +7 -4
- package/action/orchestrator.js +94 -52
- package/core/clause.js +1 -1
- package/core/ent.d.ts +17 -10
- package/core/ent.js +27 -4
- package/package.json +1 -1
- package/schema/schema.d.ts +4 -0
- package/scripts/custom_compiler.js +0 -0
- package/scripts/custom_graphql.js +0 -0
- package/testutils/builder.d.ts +4 -3
- package/testutils/builder.js +11 -4
- package/testutils/db_mock.js +2 -2
- package/testutils/ent-graphql-tests/index.js +5 -5
- package/testutils/fake_data/test_helpers.js +1 -2
- package/testutils/parse_sql.js +1 -1
package/action/action.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare enum WriteOperation {
|
|
|
7
7
|
Delete = "delete"
|
|
8
8
|
}
|
|
9
9
|
export interface Builder<T extends Ent> {
|
|
10
|
-
existingEnt?:
|
|
10
|
+
existingEnt?: T;
|
|
11
11
|
ent: EntConstructor<T>;
|
|
12
12
|
placeholderID: ID;
|
|
13
13
|
readonly viewer: Viewer;
|
|
@@ -27,9 +27,8 @@ export interface Changeset<T extends Ent> {
|
|
|
27
27
|
executor(): Executor;
|
|
28
28
|
viewer: Viewer;
|
|
29
29
|
placeholderID: ID;
|
|
30
|
-
ent: EntConstructor<T>;
|
|
31
30
|
changesets?: Changeset<Ent>[];
|
|
32
|
-
dependencies?: Map<ID, Builder<
|
|
31
|
+
dependencies?: Map<ID, Builder<Ent>>;
|
|
33
32
|
}
|
|
34
33
|
export declare type TriggerReturn = void | Promise<Changeset<Ent> | void | (Changeset<Ent> | void)[]> | Promise<Changeset<Ent>>[];
|
|
35
34
|
export interface Trigger<T extends Ent> {
|
package/action/executor.d.ts
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import { ID, Data, Ent, Viewer,
|
|
1
|
+
import { ID, Data, Ent, Viewer, Context } from "../core/base";
|
|
2
2
|
import { DataOperation } from "../core/ent";
|
|
3
|
-
import { Changeset, Executor } from "../action";
|
|
3
|
+
import { Changeset, Executor } from "../action/action";
|
|
4
4
|
import { Builder } from "../action";
|
|
5
5
|
import { OrchestratorOptions } from "./orchestrator";
|
|
6
6
|
import { Queryer } from "../core/db";
|
|
7
7
|
export declare class ListBasedExecutor<T extends Ent> implements Executor {
|
|
8
8
|
private viewer;
|
|
9
9
|
placeholderID: ID;
|
|
10
|
-
private ent;
|
|
11
10
|
private operations;
|
|
12
11
|
private options?;
|
|
13
12
|
private idx;
|
|
14
|
-
constructor(viewer: Viewer, placeholderID: ID,
|
|
13
|
+
constructor(viewer: Viewer, placeholderID: ID, operations: DataOperation<T>[], options?: OrchestratorOptions<T, Data> | undefined);
|
|
15
14
|
private lastOp;
|
|
16
15
|
private createdEnt;
|
|
17
16
|
resolveValue(val: ID): Ent | null;
|
|
18
17
|
[Symbol.iterator](): this;
|
|
19
|
-
next(): IteratorResult<DataOperation
|
|
18
|
+
next(): IteratorResult<DataOperation<T>>;
|
|
20
19
|
executeObservers(): Promise<void>;
|
|
21
20
|
execute(): Promise<void>;
|
|
22
21
|
preFetch?(queryer: Queryer, context: Context): Promise<void>;
|
|
@@ -25,23 +24,19 @@ export declare class ListBasedExecutor<T extends Ent> implements Executor {
|
|
|
25
24
|
export declare class ComplexExecutor<T extends Ent> implements Executor {
|
|
26
25
|
private viewer;
|
|
27
26
|
placeholderID: ID;
|
|
28
|
-
private ent;
|
|
29
|
-
private options?;
|
|
30
27
|
private idx;
|
|
31
28
|
private mapper;
|
|
32
29
|
private lastOp;
|
|
33
30
|
private allOperations;
|
|
34
|
-
private changesetMap;
|
|
35
|
-
private nodeOpMap;
|
|
36
31
|
private executors;
|
|
37
|
-
constructor(viewer: Viewer, placeholderID: ID,
|
|
32
|
+
constructor(viewer: Viewer, placeholderID: ID, operations: DataOperation[], dependencies: Map<ID, Builder<T>>, changesets: Changeset<T>[], options?: OrchestratorOptions<T, Data>);
|
|
38
33
|
[Symbol.iterator](): this;
|
|
39
34
|
private handleCreatedEnt;
|
|
40
|
-
next(): IteratorResult<DataOperation
|
|
35
|
+
next(): IteratorResult<DataOperation<Ent>>;
|
|
41
36
|
resolveValue(val: ID): Ent | null;
|
|
42
37
|
executeObservers(): Promise<void>;
|
|
43
38
|
execute(): Promise<void>;
|
|
44
39
|
preFetch?(queryer: Queryer, context: Context): Promise<void>;
|
|
45
40
|
postFetch?(queryer: Queryer, context: Context): Promise<void>;
|
|
46
41
|
}
|
|
47
|
-
export declare function executeOperations(executor: Executor, context?: Context, trackOps?: true): Promise<DataOperation[]>;
|
|
42
|
+
export declare function executeOperations(executor: Executor, context?: Context, trackOps?: true): Promise<DataOperation<Ent>[]>;
|
package/action/executor.js
CHANGED
|
@@ -9,10 +9,9 @@ const db_1 = __importDefault(require("../core/db"));
|
|
|
9
9
|
const logger_1 = require("../core/logger");
|
|
10
10
|
// private to ent
|
|
11
11
|
class ListBasedExecutor {
|
|
12
|
-
constructor(viewer, placeholderID,
|
|
12
|
+
constructor(viewer, placeholderID, operations, options) {
|
|
13
13
|
this.viewer = viewer;
|
|
14
14
|
this.placeholderID = placeholderID;
|
|
15
|
-
this.ent = ent;
|
|
16
15
|
this.operations = operations;
|
|
17
16
|
this.options = options;
|
|
18
17
|
this.idx = 0;
|
|
@@ -27,8 +26,9 @@ class ListBasedExecutor {
|
|
|
27
26
|
[Symbol.iterator]() {
|
|
28
27
|
return this;
|
|
29
28
|
}
|
|
29
|
+
// returns true and null|undefined when done
|
|
30
30
|
next() {
|
|
31
|
-
let createdEnt = getCreatedEnt(this.viewer, this.lastOp
|
|
31
|
+
let createdEnt = getCreatedEnt(this.viewer, this.lastOp);
|
|
32
32
|
if (createdEnt) {
|
|
33
33
|
this.createdEnt = createdEnt;
|
|
34
34
|
}
|
|
@@ -36,6 +36,10 @@ class ListBasedExecutor {
|
|
|
36
36
|
const op = this.operations[this.idx];
|
|
37
37
|
this.idx++;
|
|
38
38
|
this.lastOp = op;
|
|
39
|
+
// reset since this could be called multiple times. not needed if we have getSortedOps or something like that
|
|
40
|
+
if (done) {
|
|
41
|
+
this.idx = 0;
|
|
42
|
+
}
|
|
39
43
|
return {
|
|
40
44
|
value: op,
|
|
41
45
|
done: done,
|
|
@@ -74,30 +78,24 @@ class ListBasedExecutor {
|
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
exports.ListBasedExecutor = ListBasedExecutor;
|
|
77
|
-
function getCreatedEnt(viewer, op
|
|
78
|
-
if (op && op.
|
|
79
|
-
|
|
80
|
-
if (row) {
|
|
81
|
-
return new ent(viewer, row);
|
|
82
|
-
}
|
|
81
|
+
function getCreatedEnt(viewer, op) {
|
|
82
|
+
if (op && op.createdEnt) {
|
|
83
|
+
return op.createdEnt(viewer);
|
|
83
84
|
}
|
|
84
85
|
return null;
|
|
85
86
|
}
|
|
86
87
|
class ComplexExecutor {
|
|
87
|
-
constructor(viewer, placeholderID,
|
|
88
|
+
constructor(viewer, placeholderID, operations, dependencies, changesets, options) {
|
|
88
89
|
this.viewer = viewer;
|
|
89
90
|
this.placeholderID = placeholderID;
|
|
90
|
-
this.ent = ent;
|
|
91
|
-
this.options = options;
|
|
92
91
|
this.idx = 0;
|
|
93
92
|
this.mapper = new Map();
|
|
94
93
|
this.allOperations = [];
|
|
95
|
-
this.changesetMap = new Map();
|
|
96
|
-
this.nodeOpMap = new Map();
|
|
97
94
|
this.executors = [];
|
|
98
95
|
let graph = (0, graph_data_structure_1.default)();
|
|
96
|
+
const changesetMap = new Map();
|
|
99
97
|
const impl = (c) => {
|
|
100
|
-
|
|
98
|
+
changesetMap.set(c.placeholderID.toString(), c);
|
|
101
99
|
graph.addNode(c.placeholderID.toString());
|
|
102
100
|
if (c.dependencies) {
|
|
103
101
|
for (let [key, builder] of c.dependencies) {
|
|
@@ -117,20 +115,19 @@ class ComplexExecutor {
|
|
|
117
115
|
impl({
|
|
118
116
|
viewer: this.viewer,
|
|
119
117
|
placeholderID: this.placeholderID,
|
|
120
|
-
ent: this.ent,
|
|
121
118
|
changesets: changesets,
|
|
122
119
|
dependencies: dependencies,
|
|
123
120
|
executor: () => {
|
|
124
|
-
return new ListBasedExecutor(this.viewer, this.placeholderID,
|
|
121
|
+
return new ListBasedExecutor(this.viewer, this.placeholderID, operations, options);
|
|
125
122
|
},
|
|
126
123
|
});
|
|
127
124
|
// use a set to handle repeated ops because of how the executor logic currently works
|
|
128
|
-
// TODO: this logic
|
|
125
|
+
// TODO: can this logic be rewritten to not have a set yet avoid duplicates?
|
|
129
126
|
let nodeOps = new Set();
|
|
130
127
|
let remainOps = new Set();
|
|
131
128
|
let sorted = graph.topologicalSort(graph.nodes());
|
|
132
129
|
sorted.forEach((node) => {
|
|
133
|
-
let c =
|
|
130
|
+
let c = changesetMap.get(node);
|
|
134
131
|
if (!c) {
|
|
135
132
|
// phew. expect it to be handled somewhere else
|
|
136
133
|
// we can just skip it and expect the resolver to handle this correctly
|
|
@@ -143,9 +140,8 @@ class ComplexExecutor {
|
|
|
143
140
|
// get ordered list of ops
|
|
144
141
|
let executor = c.executor();
|
|
145
142
|
for (let op of executor) {
|
|
146
|
-
if (op.
|
|
143
|
+
if (op.createdEnt) {
|
|
147
144
|
nodeOps.add(op);
|
|
148
|
-
this.nodeOpMap.set(op, c);
|
|
149
145
|
}
|
|
150
146
|
else {
|
|
151
147
|
remainOps.add(op);
|
|
@@ -165,26 +161,30 @@ class ComplexExecutor {
|
|
|
165
161
|
return this;
|
|
166
162
|
}
|
|
167
163
|
handleCreatedEnt() {
|
|
168
|
-
|
|
169
|
-
if (!c) {
|
|
170
|
-
// nothing to do here
|
|
164
|
+
if (!this.lastOp) {
|
|
171
165
|
return;
|
|
172
166
|
}
|
|
173
|
-
let createdEnt = getCreatedEnt(this.viewer, this.lastOp
|
|
167
|
+
let createdEnt = getCreatedEnt(this.viewer, this.lastOp);
|
|
174
168
|
if (!createdEnt) {
|
|
175
169
|
return;
|
|
176
170
|
}
|
|
177
|
-
|
|
171
|
+
const placeholderID = this.lastOp.placeholderID;
|
|
172
|
+
if (!placeholderID) {
|
|
173
|
+
console.error(`op ${this.lastOp} which implements getCreatedEnt doesn't have a placeholderID`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
178
176
|
this.mapper.set(placeholderID, createdEnt);
|
|
179
177
|
}
|
|
180
178
|
next() {
|
|
181
|
-
|
|
182
|
-
this.handleCreatedEnt();
|
|
183
|
-
}
|
|
179
|
+
this.handleCreatedEnt();
|
|
184
180
|
const done = this.idx === this.allOperations.length;
|
|
185
181
|
const op = this.allOperations[this.idx];
|
|
186
182
|
this.idx++;
|
|
187
183
|
this.lastOp = op;
|
|
184
|
+
// reset since this could be called multiple times. not needed if we have getSortedOps or something like that
|
|
185
|
+
if (done) {
|
|
186
|
+
this.idx = 0;
|
|
187
|
+
}
|
|
188
188
|
return {
|
|
189
189
|
value: op,
|
|
190
190
|
done: done,
|
|
@@ -195,6 +195,12 @@ class ComplexExecutor {
|
|
|
195
195
|
if (ent) {
|
|
196
196
|
return ent;
|
|
197
197
|
}
|
|
198
|
+
for (const c of this.executors) {
|
|
199
|
+
const ent = c.resolveValue(val);
|
|
200
|
+
if (ent) {
|
|
201
|
+
return ent;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
198
204
|
return null;
|
|
199
205
|
}
|
|
200
206
|
async executeObservers() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Viewer, Ent } from "../core/base";
|
|
1
|
+
import { Viewer, Ent, Data } from "../core/base";
|
|
2
2
|
import { Action, WriteOperation, Builder, Trigger, Observer, Changeset, Validator } from "./action";
|
|
3
3
|
export interface ActionOptions<T extends Ent> {
|
|
4
4
|
existingEnt?: T | null;
|
|
@@ -35,4 +35,6 @@ export declare class BaseAction<T extends Ent> implements Action<T> {
|
|
|
35
35
|
interface BuilderConstructor<T extends Ent> {
|
|
36
36
|
new (viewer: Viewer, operation: WriteOperation, action: Action<T>, existingEnt?: T | undefined): EntBuilder<T>;
|
|
37
37
|
}
|
|
38
|
+
export declare function updateRawObject<TEnt extends Ent, TInput extends Data>(viewer: Viewer, builderCtr: BuilderConstructor<TEnt>, existingEnt: TEnt, input: TInput): Promise<TEnt>;
|
|
39
|
+
export declare function getSimpleEditAction<TEnt extends Ent, TInput extends Data>(viewer: Viewer, builderCtr: BuilderConstructor<TEnt>, existingEnt: TEnt, input: TInput): Action<TEnt>;
|
|
38
40
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BaseAction = void 0;
|
|
3
|
+
exports.getSimpleEditAction = exports.updateRawObject = exports.BaseAction = void 0;
|
|
4
4
|
const privacy_1 = require("../core/privacy");
|
|
5
5
|
const action_1 = require("./action");
|
|
6
6
|
class BaseAction {
|
|
@@ -66,3 +66,28 @@ class BaseAction {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
exports.BaseAction = BaseAction;
|
|
69
|
+
// this provides a way to just update a row in the database.
|
|
70
|
+
// skips privacy, triggers, observers, etc
|
|
71
|
+
// does do field validation
|
|
72
|
+
// note that only editable fields in the builder can be passed here
|
|
73
|
+
async function updateRawObject(viewer, builderCtr, existingEnt, input) {
|
|
74
|
+
const action = new BaseAction(viewer, builderCtr, {
|
|
75
|
+
existingEnt: existingEnt,
|
|
76
|
+
operation: action_1.WriteOperation.Edit,
|
|
77
|
+
input,
|
|
78
|
+
});
|
|
79
|
+
return action.saveX();
|
|
80
|
+
}
|
|
81
|
+
exports.updateRawObject = updateRawObject;
|
|
82
|
+
// creates an action which has no privacy, triggers, observers etc
|
|
83
|
+
// does do field validation
|
|
84
|
+
// useful to batch a bunch of writes together with BaseAction.bulkAction
|
|
85
|
+
// note that only editable fields in the builder can be passed here
|
|
86
|
+
function getSimpleEditAction(viewer, builderCtr, existingEnt, input) {
|
|
87
|
+
return new BaseAction(viewer, builderCtr, {
|
|
88
|
+
existingEnt: existingEnt,
|
|
89
|
+
operation: action_1.WriteOperation.Edit,
|
|
90
|
+
input,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
exports.getSimpleEditAction = getSimpleEditAction;
|
package/action/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { WriteOperation, Builder,
|
|
1
|
+
export { WriteOperation, Builder, Changeset, Trigger, Observer, Validator, Action, saveBuilder, saveBuilderX, setEdgeTypeInGroup, TriggerReturn, } from "./action";
|
|
2
2
|
export { OrchestratorOptions, Orchestrator, EntChangeset, EdgeInputData, } from "./orchestrator";
|
|
3
3
|
export { DenyIfBuilder, AllowIfBuilder } from "./privacy";
|
package/action/orchestrator.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ID, Data, Ent, Viewer, EntConstructor, LoadEntOptions } from "../core/base";
|
|
2
2
|
import { AssocEdgeInputOptions, DataOperation } from "../core/ent";
|
|
3
3
|
import { SchemaInputType } from "../schema/schema";
|
|
4
|
-
import { Changeset, Executor } from "../action";
|
|
4
|
+
import { Changeset, Executor } from "../action/action";
|
|
5
5
|
import { WriteOperation, Builder, Action } from "../action";
|
|
6
6
|
export interface OrchestratorOptions<T extends Ent, TData extends Data> {
|
|
7
7
|
viewer: Viewer;
|
|
@@ -59,12 +59,14 @@ export declare class Orchestrator<T extends Ent> {
|
|
|
59
59
|
private validators;
|
|
60
60
|
private isBuilder;
|
|
61
61
|
private getFieldsWithDefaultValues;
|
|
62
|
+
private hasData;
|
|
63
|
+
private transformFieldValue;
|
|
62
64
|
private formatAndValidateFields;
|
|
63
65
|
valid(): Promise<boolean>;
|
|
64
66
|
validX(): Promise<void>;
|
|
65
67
|
build(): Promise<EntChangeset<T>>;
|
|
66
68
|
private viewerForEntLoad;
|
|
67
|
-
returnedRow
|
|
69
|
+
private returnedRow;
|
|
68
70
|
editedEnt(): Promise<T | null>;
|
|
69
71
|
editedEntX(): Promise<T>;
|
|
70
72
|
}
|
|
@@ -73,10 +75,11 @@ export declare class EntChangeset<T extends Ent> implements Changeset<T> {
|
|
|
73
75
|
readonly placeholderID: ID;
|
|
74
76
|
readonly ent: EntConstructor<T>;
|
|
75
77
|
operations: DataOperation[];
|
|
76
|
-
dependencies?: Map<ID, Builder<
|
|
78
|
+
dependencies?: Map<ID, Builder<Ent>> | undefined;
|
|
77
79
|
changesets?: Changeset<Ent>[] | undefined;
|
|
78
80
|
private options?;
|
|
79
|
-
|
|
81
|
+
private _executor;
|
|
82
|
+
constructor(viewer: Viewer, placeholderID: ID, ent: EntConstructor<T>, operations: DataOperation[], dependencies?: Map<ID, Builder<Ent>> | undefined, changesets?: Changeset<Ent>[] | undefined, options?: OrchestratorOptions<T, Data> | undefined);
|
|
80
83
|
executor(): Executor;
|
|
81
84
|
}
|
|
82
85
|
export {};
|
package/action/orchestrator.js
CHANGED
|
@@ -146,6 +146,8 @@ class Orchestrator {
|
|
|
146
146
|
tableName: this.options.tableName,
|
|
147
147
|
fieldsToResolve: this.fieldsToResolve,
|
|
148
148
|
key: this.options.key,
|
|
149
|
+
ent: this.options.loaderOptions.ent,
|
|
150
|
+
placeholderID: this.options.builder.placeholderID,
|
|
149
151
|
};
|
|
150
152
|
if (this.logValues) {
|
|
151
153
|
opts.fieldsToLog = this.logValues;
|
|
@@ -301,6 +303,7 @@ class Orchestrator {
|
|
|
301
303
|
getFieldsWithDefaultValues(builder, schemaFields, action) {
|
|
302
304
|
const editedFields = this.options.editedFields();
|
|
303
305
|
let data = {};
|
|
306
|
+
let defaultData = {};
|
|
304
307
|
let input = action?.getInput() || {};
|
|
305
308
|
let updateInput = false;
|
|
306
309
|
for (const [fieldName, field] of schemaFields) {
|
|
@@ -328,23 +331,79 @@ class Orchestrator {
|
|
|
328
331
|
// TODO special case this if this is the only thing changing and don't do the write.
|
|
329
332
|
}
|
|
330
333
|
}
|
|
331
|
-
|
|
334
|
+
if (value !== undefined) {
|
|
335
|
+
data[dbKey] = value;
|
|
336
|
+
}
|
|
332
337
|
if (defaultValue !== undefined) {
|
|
333
338
|
updateInput = true;
|
|
334
|
-
|
|
339
|
+
defaultData[dbKey] = defaultValue;
|
|
335
340
|
this.defaultFieldsByFieldName[fieldName] = defaultValue;
|
|
336
341
|
// TODO related to #510. we need this logic to be consistent so do this all in TypeScript or get it from go somehow
|
|
337
342
|
this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(fieldName)] = defaultValue;
|
|
338
343
|
}
|
|
339
344
|
}
|
|
340
|
-
if
|
|
341
|
-
|
|
342
|
-
|
|
345
|
+
// if there's data changing, add data
|
|
346
|
+
if (this.hasData(data)) {
|
|
347
|
+
data = {
|
|
348
|
+
...data,
|
|
349
|
+
...defaultData,
|
|
350
|
+
};
|
|
351
|
+
if (updateInput && this.options.updateInput) {
|
|
352
|
+
// this basically fixes #605. just needs to be exposed correctly
|
|
353
|
+
this.options.updateInput(this.defaultFieldsByTSName);
|
|
354
|
+
}
|
|
343
355
|
}
|
|
344
356
|
return data;
|
|
345
357
|
}
|
|
358
|
+
hasData(data) {
|
|
359
|
+
for (const _k in data) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
async transformFieldValue(field, dbKey, value) {
|
|
365
|
+
// now format and validate...
|
|
366
|
+
if (value === null) {
|
|
367
|
+
if (!field.nullable) {
|
|
368
|
+
throw new Error(`field ${field.name} set to null for non-nullable field`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
else if (value === undefined) {
|
|
372
|
+
if (!field.nullable &&
|
|
373
|
+
// required field can be skipped if server default set
|
|
374
|
+
// not checking defaultValueOnCreate() or defaultValueOnEdit() as that's set above
|
|
375
|
+
// not setting server default as we're depending on the database handling that.
|
|
376
|
+
// server default allowed
|
|
377
|
+
field.serverDefault === undefined &&
|
|
378
|
+
this.options.operation === action_1.WriteOperation.Insert) {
|
|
379
|
+
throw new Error(`required field ${field.name} not set`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else if (this.isBuilder(value)) {
|
|
383
|
+
let builder = value;
|
|
384
|
+
// keep track of dependencies to resolve
|
|
385
|
+
this.dependencies.set(builder.placeholderID, builder);
|
|
386
|
+
// keep track of fields to resolve
|
|
387
|
+
this.fieldsToResolve.push(dbKey);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
if (field.valid) {
|
|
391
|
+
// TODO this could be async. handle this better
|
|
392
|
+
let valid = await Promise.resolve(field.valid(value));
|
|
393
|
+
if (!valid) {
|
|
394
|
+
throw new Error(`invalid field ${field.name} with value ${value}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (field.format) {
|
|
398
|
+
// TODO this could be async e.g. password. handle this better
|
|
399
|
+
value = await Promise.resolve(field.format(value));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return value;
|
|
403
|
+
}
|
|
346
404
|
async formatAndValidateFields(schemaFields) {
|
|
347
|
-
|
|
405
|
+
const op = this.options.operation;
|
|
406
|
+
if (op === action_1.WriteOperation.Delete) {
|
|
348
407
|
return;
|
|
349
408
|
}
|
|
350
409
|
const editedFields = this.options.editedFields();
|
|
@@ -353,53 +412,32 @@ class Orchestrator {
|
|
|
353
412
|
let logValues = {};
|
|
354
413
|
for (const [fieldName, field] of schemaFields) {
|
|
355
414
|
let value = editedFields.get(fieldName);
|
|
356
|
-
if (value === undefined) {
|
|
415
|
+
if (value === undefined && op === action_1.WriteOperation.Insert) {
|
|
357
416
|
// null allowed
|
|
358
417
|
value = this.defaultFieldsByFieldName[fieldName];
|
|
359
418
|
}
|
|
360
419
|
let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(field.name);
|
|
361
|
-
|
|
362
|
-
if (value === null) {
|
|
363
|
-
if (!field.nullable) {
|
|
364
|
-
throw new Error(`field ${field.name} set to null for non-nullable field`);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
else if (value === undefined) {
|
|
368
|
-
if (!field.nullable &&
|
|
369
|
-
// required field can be skipped if server default set
|
|
370
|
-
// not checking defaultValueOnCreate() or defaultValueOnEdit() as that's set above
|
|
371
|
-
// not setting server default as we're depending on the database handling that.
|
|
372
|
-
// server default allowed
|
|
373
|
-
field.serverDefault === undefined &&
|
|
374
|
-
this.options.operation === action_1.WriteOperation.Insert) {
|
|
375
|
-
throw new Error(`required field ${field.name} not set`);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
else if (this.isBuilder(value)) {
|
|
379
|
-
let builder = value;
|
|
380
|
-
// keep track of dependencies to resolve
|
|
381
|
-
this.dependencies.set(builder.placeholderID, builder);
|
|
382
|
-
// keep track of fields to resolve
|
|
383
|
-
this.fieldsToResolve.push(dbKey);
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
if (field.valid) {
|
|
387
|
-
// TODO this could be async. handle this better
|
|
388
|
-
let valid = await Promise.resolve(field.valid(value));
|
|
389
|
-
if (!valid) {
|
|
390
|
-
throw new Error(`invalid field ${field.name} with value ${value}`);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
if (field.format) {
|
|
394
|
-
// TODO this could be async e.g. password. handle this better
|
|
395
|
-
value = await Promise.resolve(field.format(value));
|
|
396
|
-
}
|
|
397
|
-
}
|
|
420
|
+
value = await this.transformFieldValue(field, dbKey, value);
|
|
398
421
|
if (value !== undefined) {
|
|
399
422
|
data[dbKey] = value;
|
|
400
423
|
logValues[dbKey] = field.logValue(value);
|
|
401
424
|
}
|
|
402
425
|
}
|
|
426
|
+
// we ignored default values while editing.
|
|
427
|
+
// if we're editing and there's data, add default values
|
|
428
|
+
if (op === action_1.WriteOperation.Edit && this.hasData(data)) {
|
|
429
|
+
for (const fieldName in this.defaultFieldsByFieldName) {
|
|
430
|
+
const defaultValue = this.defaultFieldsByFieldName[fieldName];
|
|
431
|
+
let field = schemaFields.get(fieldName);
|
|
432
|
+
let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(field.name);
|
|
433
|
+
// no value, let's just default
|
|
434
|
+
if (data[dbKey] === undefined) {
|
|
435
|
+
const value = await this.transformFieldValue(field, dbKey, defaultValue);
|
|
436
|
+
data[dbKey] = value;
|
|
437
|
+
logValues[dbKey] = field.logValue(value);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
403
441
|
this.validatedFields = data;
|
|
404
442
|
this.logValues = logValues;
|
|
405
443
|
}
|
|
@@ -432,8 +470,8 @@ class Orchestrator {
|
|
|
432
470
|
return action.viewerForEntLoad(data);
|
|
433
471
|
}
|
|
434
472
|
async returnedRow() {
|
|
435
|
-
if (this.mainOp && this.mainOp.
|
|
436
|
-
return this.mainOp.
|
|
473
|
+
if (this.mainOp && this.mainOp.returnedRow) {
|
|
474
|
+
return this.mainOp.returnedRow();
|
|
437
475
|
}
|
|
438
476
|
return null;
|
|
439
477
|
}
|
|
@@ -475,13 +513,17 @@ class EntChangeset {
|
|
|
475
513
|
this.options = options;
|
|
476
514
|
}
|
|
477
515
|
executor() {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (this.changesets?.length) {
|
|
482
|
-
|
|
516
|
+
if (this._executor) {
|
|
517
|
+
return this._executor;
|
|
518
|
+
}
|
|
519
|
+
if (!this.changesets?.length) {
|
|
520
|
+
// if we have dependencies but no changesets, we just need a simple
|
|
521
|
+
// executor and depend on something else in the stack to handle this correctly
|
|
522
|
+
// ComplexExecutor which could be a parent of this should make sure the dependency
|
|
523
|
+
// is resolved beforehand
|
|
524
|
+
return (this._executor = new executor_1.ListBasedExecutor(this.viewer, this.placeholderID, this.operations, this.options));
|
|
483
525
|
}
|
|
484
|
-
return new executor_1.
|
|
526
|
+
return (this._executor = new executor_1.ComplexExecutor(this.viewer, this.placeholderID, this.operations, this.dependencies || new Map(), this.changesets || [], this.options));
|
|
485
527
|
}
|
|
486
528
|
}
|
|
487
529
|
exports.EntChangeset = EntChangeset;
|
package/core/clause.js
CHANGED
|
@@ -177,7 +177,7 @@ function Or(...args) {
|
|
|
177
177
|
return new compositeClause(args, " OR ");
|
|
178
178
|
}
|
|
179
179
|
exports.Or = Or;
|
|
180
|
-
// todo
|
|
180
|
+
// TODO this breaks if values.length ===1 and array. todo fix
|
|
181
181
|
function In(col, ...values) {
|
|
182
182
|
return new inClause(col, values);
|
|
183
183
|
}
|
package/core/ent.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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 } from "./base";
|
|
3
|
-
import { Executor } from "../action";
|
|
2
|
+
import { Viewer, Ent, ID, LoadRowsOptions, LoadRowOptions, Data, DataOptions, QueryableDataOptions, EditRowOptions, LoadEntOptions, LoadCustomEntOptions, EdgeQueryableDataOptions, Context, SelectBaseDataOptions, CreateRowOptions, QueryDataOptions, EntConstructor } from "./base";
|
|
3
|
+
import { Executor } from "../action/action";
|
|
4
4
|
import * as clause from "./clause";
|
|
5
5
|
import { Builder } from "../action";
|
|
6
6
|
import DataLoader from "dataloader";
|
|
@@ -39,27 +39,34 @@ interface GroupQueryOptions {
|
|
|
39
39
|
limit: number;
|
|
40
40
|
}
|
|
41
41
|
export declare function buildGroupQuery(options: GroupQueryOptions): [string, clause.Clause];
|
|
42
|
-
export interface DataOperation {
|
|
42
|
+
export interface DataOperation<T extends Ent = Ent> {
|
|
43
43
|
preFetch?(queryer: Queryer, context?: Context): Promise<void>;
|
|
44
44
|
performWriteSync(queryer: SyncQueryer, context?: Context): void;
|
|
45
45
|
performWrite(queryer: Queryer, context?: Context): Promise<void>;
|
|
46
|
-
|
|
46
|
+
placeholderID?: ID;
|
|
47
|
+
returnedRow?(): Data | null;
|
|
48
|
+
createdEnt?(viewer: Viewer): T | null;
|
|
47
49
|
resolve?(executor: Executor): void;
|
|
48
50
|
postFetch?(queryer: Queryer, context?: Context): Promise<void>;
|
|
49
51
|
}
|
|
50
|
-
export interface EditNodeOptions extends EditRowOptions {
|
|
52
|
+
export interface EditNodeOptions<T extends Ent> extends EditRowOptions {
|
|
51
53
|
fieldsToResolve: string[];
|
|
54
|
+
ent: EntConstructor<T>;
|
|
55
|
+
placeholderID?: ID;
|
|
52
56
|
}
|
|
53
|
-
export declare class EditNodeOperation implements DataOperation {
|
|
54
|
-
options: EditNodeOptions
|
|
57
|
+
export declare class EditNodeOperation<T extends Ent> implements DataOperation {
|
|
58
|
+
options: EditNodeOptions<T>;
|
|
55
59
|
private existingEnt;
|
|
56
60
|
row: Data | null;
|
|
57
|
-
|
|
61
|
+
placeholderID?: ID | undefined;
|
|
62
|
+
constructor(options: EditNodeOptions<T>, existingEnt?: Ent | null);
|
|
58
63
|
resolve<T extends Ent>(executor: Executor): void;
|
|
64
|
+
private hasData;
|
|
59
65
|
performWrite(queryer: Queryer, context?: Context): Promise<void>;
|
|
60
|
-
reloadRow
|
|
66
|
+
private reloadRow;
|
|
61
67
|
performWriteSync(queryer: SyncQueryer, context?: Context): void;
|
|
62
|
-
|
|
68
|
+
returnedRow(): Data | null;
|
|
69
|
+
createdEnt(viewer: Viewer): T | null;
|
|
63
70
|
}
|
|
64
71
|
export declare class EdgeOperation implements DataOperation {
|
|
65
72
|
edgeInput: AssocEdgeInput;
|
package/core/ent.js
CHANGED
|
@@ -401,6 +401,7 @@ class EditNodeOperation {
|
|
|
401
401
|
constructor(options, existingEnt = null) {
|
|
402
402
|
this.options = options;
|
|
403
403
|
this.existingEnt = existingEnt;
|
|
404
|
+
this.placeholderID = options.placeholderID;
|
|
404
405
|
}
|
|
405
406
|
resolve(executor) {
|
|
406
407
|
if (!this.options.fieldsToResolve.length) {
|
|
@@ -420,13 +421,24 @@ class EditNodeOperation {
|
|
|
420
421
|
});
|
|
421
422
|
this.options.fields = fields;
|
|
422
423
|
}
|
|
424
|
+
hasData(data) {
|
|
425
|
+
for (const _k in data) {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
423
430
|
async performWrite(queryer, context) {
|
|
424
431
|
let options = {
|
|
425
432
|
...this.options,
|
|
426
433
|
context,
|
|
427
434
|
};
|
|
428
435
|
if (this.existingEnt) {
|
|
429
|
-
this.
|
|
436
|
+
if (this.hasData(options.fields)) {
|
|
437
|
+
this.row = await editRow(queryer, options, this.existingEnt.id, "RETURNING *");
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
this.row = this.existingEnt["data"];
|
|
441
|
+
}
|
|
430
442
|
}
|
|
431
443
|
else {
|
|
432
444
|
this.row = await createRow(queryer, options, "RETURNING *");
|
|
@@ -454,8 +466,13 @@ class EditNodeOperation {
|
|
|
454
466
|
context,
|
|
455
467
|
};
|
|
456
468
|
if (this.existingEnt) {
|
|
457
|
-
|
|
458
|
-
|
|
469
|
+
if (this.hasData(this.options.fields)) {
|
|
470
|
+
editRowSync(queryer, options, this.existingEnt.id, "RETURNING *");
|
|
471
|
+
this.reloadRow(queryer, this.existingEnt.id, options);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
this.row = this.existingEnt["data"];
|
|
475
|
+
}
|
|
459
476
|
}
|
|
460
477
|
else {
|
|
461
478
|
createRowSync(queryer, options, "RETURNING *");
|
|
@@ -463,9 +480,15 @@ class EditNodeOperation {
|
|
|
463
480
|
this.reloadRow(queryer, id, options);
|
|
464
481
|
}
|
|
465
482
|
}
|
|
466
|
-
|
|
483
|
+
returnedRow() {
|
|
467
484
|
return this.row;
|
|
468
485
|
}
|
|
486
|
+
createdEnt(viewer) {
|
|
487
|
+
if (!this.row) {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
return new this.options.ent(viewer, this.row);
|
|
491
|
+
}
|
|
469
492
|
}
|
|
470
493
|
exports.EditNodeOperation = EditNodeOperation;
|
|
471
494
|
class EdgeOperation {
|
package/package.json
CHANGED
package/schema/schema.d.ts
CHANGED
|
@@ -168,6 +168,10 @@ export interface Action {
|
|
|
168
168
|
graphQLName?: string;
|
|
169
169
|
hideFromGraphQL?: boolean;
|
|
170
170
|
actionOnlyFields?: ActionField[];
|
|
171
|
+
excludedFields?: string[];
|
|
172
|
+
optionalFields?: string[];
|
|
173
|
+
requiredFields?: string[];
|
|
174
|
+
noFields?: boolean;
|
|
171
175
|
}
|
|
172
176
|
export declare const NoFields = "__NO_FIELDS__";
|
|
173
177
|
export declare function requiredField(field: string): string;
|
|
File without changes
|
|
File without changes
|
package/testutils/builder.d.ts
CHANGED
|
@@ -66,12 +66,12 @@ export declare class SimpleBuilder<T extends Ent> implements Builder<T> {
|
|
|
66
66
|
viewer: Viewer;
|
|
67
67
|
private schema;
|
|
68
68
|
operation: WriteOperation;
|
|
69
|
-
existingEnt:
|
|
69
|
+
existingEnt: T | undefined;
|
|
70
70
|
ent: EntConstructor<T>;
|
|
71
71
|
placeholderID: ID;
|
|
72
72
|
orchestrator: Orchestrator<T>;
|
|
73
73
|
fields: Map<string, any>;
|
|
74
|
-
constructor(viewer: Viewer, schema: BuilderSchema<T>, fields: Map<string, any>, operation?: WriteOperation, existingEnt?:
|
|
74
|
+
constructor(viewer: Viewer, schema: BuilderSchema<T>, fields: Map<string, any>, operation?: WriteOperation, existingEnt?: T | undefined, action?: Action<T> | undefined);
|
|
75
75
|
build(): Promise<Changeset<T>>;
|
|
76
76
|
editedEnt(): Promise<T | null>;
|
|
77
77
|
editedEntX(): Promise<T>;
|
|
@@ -93,12 +93,13 @@ export declare class SimpleAction<T extends Ent> implements Action<T> {
|
|
|
93
93
|
viewerForEntLoad: viewerEntLoadFunc | undefined;
|
|
94
94
|
constructor(viewer: Viewer, schema: BuilderSchema<T>, fields: Map<string, any>, operation?: WriteOperation, existingEnt?: T | undefined);
|
|
95
95
|
getPrivacyPolicy(): import("../core/base").PrivacyPolicy;
|
|
96
|
-
getInput():
|
|
96
|
+
getInput(): Data;
|
|
97
97
|
changeset(): Promise<Changeset<T>>;
|
|
98
98
|
valid(): Promise<boolean>;
|
|
99
99
|
validX(): Promise<void>;
|
|
100
100
|
save(): Promise<T | null>;
|
|
101
101
|
saveX(): Promise<T>;
|
|
102
102
|
editedEnt(): Promise<T | null>;
|
|
103
|
+
editedEntX(): Promise<T>;
|
|
103
104
|
}
|
|
104
105
|
export {};
|
package/testutils/builder.js
CHANGED
|
@@ -185,7 +185,11 @@ class SimpleAction {
|
|
|
185
185
|
return privacy_1.AlwaysAllowPrivacyPolicy;
|
|
186
186
|
}
|
|
187
187
|
getInput() {
|
|
188
|
-
|
|
188
|
+
const ret = {};
|
|
189
|
+
for (const [k, v] of this.fields) {
|
|
190
|
+
ret[k] = v;
|
|
191
|
+
}
|
|
192
|
+
return ret;
|
|
189
193
|
}
|
|
190
194
|
changeset() {
|
|
191
195
|
return this.builder.build();
|
|
@@ -199,16 +203,19 @@ class SimpleAction {
|
|
|
199
203
|
async save() {
|
|
200
204
|
await (0, action_1.saveBuilder)(this.builder);
|
|
201
205
|
if (this.builder.operation !== action_1.WriteOperation.Delete) {
|
|
202
|
-
return
|
|
206
|
+
return this.builder.orchestrator.editedEnt();
|
|
203
207
|
}
|
|
204
208
|
return null;
|
|
205
209
|
}
|
|
206
210
|
async saveX() {
|
|
207
211
|
await (0, action_1.saveBuilderX)(this.builder);
|
|
208
|
-
return
|
|
212
|
+
return this.builder.orchestrator.editedEntX();
|
|
209
213
|
}
|
|
210
214
|
async editedEnt() {
|
|
211
|
-
return
|
|
215
|
+
return this.builder.orchestrator.editedEnt();
|
|
216
|
+
}
|
|
217
|
+
async editedEntX() {
|
|
218
|
+
return this.builder.orchestrator.editedEntX();
|
|
212
219
|
}
|
|
213
220
|
}
|
|
214
221
|
exports.SimpleAction = SimpleAction;
|
package/testutils/db_mock.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.QueryRecorder = exports.queryType = void 0;
|
|
4
4
|
const uuid_1 = require("uuid");
|
|
5
|
-
const
|
|
5
|
+
const jest_mock_1 = require("jest-mock");
|
|
6
6
|
const parse_sql_1 = require("./parse_sql");
|
|
7
7
|
const eventEmitter = {
|
|
8
8
|
on: jest.fn(),
|
|
@@ -185,7 +185,7 @@ class QueryRecorder {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
static mockPool(pool) {
|
|
188
|
-
const mockedPool = (0,
|
|
188
|
+
const mockedPool = (0, jest_mock_1.mocked)(pool, true);
|
|
189
189
|
mockedPool.mockImplementation(() => {
|
|
190
190
|
return {
|
|
191
191
|
totalCount: 1,
|
|
@@ -286,11 +286,11 @@ async function expectFromRoot(config, ...options) {
|
|
|
286
286
|
let fields = query?.getFields();
|
|
287
287
|
if (!fields) {
|
|
288
288
|
// TODO custom error?
|
|
289
|
-
|
|
289
|
+
throw new Error("schema doesn't have query or fields");
|
|
290
290
|
}
|
|
291
291
|
let field = fields[config.root];
|
|
292
292
|
if (!field) {
|
|
293
|
-
|
|
293
|
+
throw new Error(`could not find field ${config.root} in GraphQL query schema`);
|
|
294
294
|
}
|
|
295
295
|
let fieldArgs = field.args;
|
|
296
296
|
let queryParams = [];
|
|
@@ -361,7 +361,7 @@ async function expectFromRoot(config, ...options) {
|
|
|
361
361
|
expect(errors[0].message).toMatch(config.expectedError);
|
|
362
362
|
}
|
|
363
363
|
else {
|
|
364
|
-
|
|
364
|
+
throw new Error(`unhandled error ${JSON.stringify(errors)}`);
|
|
365
365
|
}
|
|
366
366
|
return st;
|
|
367
367
|
}
|
|
@@ -416,7 +416,7 @@ async function expectFromRoot(config, ...options) {
|
|
|
416
416
|
if (idx !== -1) {
|
|
417
417
|
let endIdx = part.indexOf("]");
|
|
418
418
|
if (endIdx === -1) {
|
|
419
|
-
|
|
419
|
+
throw new Error("can't have a beginning index without an end index");
|
|
420
420
|
}
|
|
421
421
|
// get the idx we care about
|
|
422
422
|
listIdx = parseInt(part.substr(idx + 1, endIdx - idx), 10);
|
|
@@ -428,7 +428,7 @@ async function expectFromRoot(config, ...options) {
|
|
|
428
428
|
if (idx !== -1) {
|
|
429
429
|
let endIdx = part.indexOf(")");
|
|
430
430
|
if (endIdx === -1) {
|
|
431
|
-
|
|
431
|
+
throw new Error("can't have a beginning index without an end index");
|
|
432
432
|
}
|
|
433
433
|
// update part
|
|
434
434
|
part = part.substr(0, idx);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createAllEvents = exports.tempDBTables = exports.setupTempDB = exports.createTestEvent = exports.edgeTableNames = exports.createEdges = exports.verifyUserToContacts = exports.verifyUserToContactRawData = exports.verifyUserToContactEdges = exports.addEdge = exports.createUserPlusFriendRequests = exports.createAllContacts = exports.inputs = exports.createTestUser = exports.getEventInput = exports.getUserInput = exports.getContactInput = void 0;
|
|
4
|
-
const assert_1 = require("assert");
|
|
5
4
|
const jest_date_mock_1 = require("jest-date-mock");
|
|
6
5
|
const viewer_1 = require("../../core/viewer");
|
|
7
6
|
const ent_1 = require("../../core/ent");
|
|
@@ -56,7 +55,7 @@ async function createTestUser(input) {
|
|
|
56
55
|
...input,
|
|
57
56
|
});
|
|
58
57
|
if (!user) {
|
|
59
|
-
|
|
58
|
+
throw new Error("error creating user");
|
|
60
59
|
}
|
|
61
60
|
return user;
|
|
62
61
|
}
|