@snowtop/ent 0.0.30-alpha → 0.0.32-alpha.10
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 +1 -1
- package/action/experimental_action.d.ts +3 -1
- package/action/experimental_action.js +26 -1
- package/action/orchestrator.d.ts +3 -1
- package/action/orchestrator.js +85 -46
- package/core/ent.d.ts +1 -0
- package/core/ent.js +19 -3
- package/graphql/index.d.ts +1 -1
- package/graphql/index.js +2 -1
- package/graphql/node_resolver.d.ts +1 -0
- package/graphql/node_resolver.js +14 -1
- package/package.json +1 -1
- package/parse_schema/parse.js +8 -0
- package/schema/base_schema.js +1 -1
- package/schema/field.d.ts +4 -1
- package/schema/field.js +39 -7
- package/schema/schema.d.ts +16 -3
- package/testutils/builder.d.ts +3 -2
- package/testutils/builder.js +6 -3
- package/testutils/ent-graphql-tests/index.d.ts +1 -0
- package/testutils/parse_sql.js +18 -2
package/action/action.d.ts
CHANGED
|
@@ -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/orchestrator.d.ts
CHANGED
|
@@ -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
|
-
|
|
69
|
+
returnedRow(): Promise<Data | null>;
|
|
68
70
|
editedEnt(): Promise<T | null>;
|
|
69
71
|
editedEntX(): Promise<T>;
|
|
70
72
|
}
|
package/action/orchestrator.js
CHANGED
|
@@ -303,6 +303,7 @@ class Orchestrator {
|
|
|
303
303
|
getFieldsWithDefaultValues(builder, schemaFields, action) {
|
|
304
304
|
const editedFields = this.options.editedFields();
|
|
305
305
|
let data = {};
|
|
306
|
+
let defaultData = {};
|
|
306
307
|
let input = action?.getInput() || {};
|
|
307
308
|
let updateInput = false;
|
|
308
309
|
for (const [fieldName, field] of schemaFields) {
|
|
@@ -327,26 +328,86 @@ class Orchestrator {
|
|
|
327
328
|
if (field.defaultValueOnEdit &&
|
|
328
329
|
this.options.operation === action_1.WriteOperation.Edit) {
|
|
329
330
|
defaultValue = field.defaultValueOnEdit(builder, input);
|
|
330
|
-
// TODO special case this if this is the only thing changing and don't do the write.
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
|
-
|
|
333
|
+
if (value !== undefined) {
|
|
334
|
+
data[dbKey] = value;
|
|
335
|
+
}
|
|
334
336
|
if (defaultValue !== undefined) {
|
|
335
337
|
updateInput = true;
|
|
336
|
-
|
|
338
|
+
defaultData[dbKey] = defaultValue;
|
|
337
339
|
this.defaultFieldsByFieldName[fieldName] = defaultValue;
|
|
338
340
|
// TODO related to #510. we need this logic to be consistent so do this all in TypeScript or get it from go somehow
|
|
339
341
|
this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(fieldName)] = defaultValue;
|
|
340
342
|
}
|
|
341
343
|
}
|
|
342
|
-
if
|
|
343
|
-
|
|
344
|
-
|
|
344
|
+
// if there's data changing, add data
|
|
345
|
+
if (this.hasData(data)) {
|
|
346
|
+
data = {
|
|
347
|
+
...data,
|
|
348
|
+
...defaultData,
|
|
349
|
+
};
|
|
350
|
+
if (updateInput && this.options.updateInput) {
|
|
351
|
+
// this basically fixes #605. just needs to be exposed correctly
|
|
352
|
+
this.options.updateInput(this.defaultFieldsByTSName);
|
|
353
|
+
}
|
|
345
354
|
}
|
|
346
355
|
return data;
|
|
347
356
|
}
|
|
357
|
+
hasData(data) {
|
|
358
|
+
for (const _k in data) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
async transformFieldValue(field, dbKey, value) {
|
|
364
|
+
// now format and validate...
|
|
365
|
+
if (value === null) {
|
|
366
|
+
if (!field.nullable) {
|
|
367
|
+
throw new Error(`field ${field.name} set to null for non-nullable field`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
else if (value === undefined) {
|
|
371
|
+
if (!field.nullable &&
|
|
372
|
+
// required field can be skipped if server default set
|
|
373
|
+
// not checking defaultValueOnCreate() or defaultValueOnEdit() as that's set above
|
|
374
|
+
// not setting server default as we're depending on the database handling that.
|
|
375
|
+
// server default allowed
|
|
376
|
+
field.serverDefault === undefined &&
|
|
377
|
+
this.options.operation === action_1.WriteOperation.Insert) {
|
|
378
|
+
throw new Error(`required field ${field.name} not set`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else if (this.isBuilder(value)) {
|
|
382
|
+
if (field.valid) {
|
|
383
|
+
const valid = await Promise.resolve(field.valid(value));
|
|
384
|
+
if (!valid) {
|
|
385
|
+
throw new Error(`invalid field ${field.name} with value ${value}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// keep track of dependencies to resolve
|
|
389
|
+
this.dependencies.set(value.placeholderID, value);
|
|
390
|
+
// keep track of fields to resolve
|
|
391
|
+
this.fieldsToResolve.push(dbKey);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
if (field.valid) {
|
|
395
|
+
// TODO this could be async. handle this better
|
|
396
|
+
const valid = await Promise.resolve(field.valid(value));
|
|
397
|
+
if (!valid) {
|
|
398
|
+
throw new Error(`invalid field ${field.name} with value ${value}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (field.format) {
|
|
402
|
+
// TODO this could be async e.g. password. handle this better
|
|
403
|
+
value = await Promise.resolve(field.format(value));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return value;
|
|
407
|
+
}
|
|
348
408
|
async formatAndValidateFields(schemaFields) {
|
|
349
|
-
|
|
409
|
+
const op = this.options.operation;
|
|
410
|
+
if (op === action_1.WriteOperation.Delete) {
|
|
350
411
|
return;
|
|
351
412
|
}
|
|
352
413
|
const editedFields = this.options.editedFields();
|
|
@@ -355,53 +416,32 @@ class Orchestrator {
|
|
|
355
416
|
let logValues = {};
|
|
356
417
|
for (const [fieldName, field] of schemaFields) {
|
|
357
418
|
let value = editedFields.get(fieldName);
|
|
358
|
-
if (value === undefined) {
|
|
419
|
+
if (value === undefined && op === action_1.WriteOperation.Insert) {
|
|
359
420
|
// null allowed
|
|
360
421
|
value = this.defaultFieldsByFieldName[fieldName];
|
|
361
422
|
}
|
|
362
423
|
let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(field.name);
|
|
363
|
-
|
|
364
|
-
if (value === null) {
|
|
365
|
-
if (!field.nullable) {
|
|
366
|
-
throw new Error(`field ${field.name} set to null for non-nullable field`);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
else if (value === undefined) {
|
|
370
|
-
if (!field.nullable &&
|
|
371
|
-
// required field can be skipped if server default set
|
|
372
|
-
// not checking defaultValueOnCreate() or defaultValueOnEdit() as that's set above
|
|
373
|
-
// not setting server default as we're depending on the database handling that.
|
|
374
|
-
// server default allowed
|
|
375
|
-
field.serverDefault === undefined &&
|
|
376
|
-
this.options.operation === action_1.WriteOperation.Insert) {
|
|
377
|
-
throw new Error(`required field ${field.name} not set`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
else if (this.isBuilder(value)) {
|
|
381
|
-
let builder = value;
|
|
382
|
-
// keep track of dependencies to resolve
|
|
383
|
-
this.dependencies.set(builder.placeholderID, builder);
|
|
384
|
-
// keep track of fields to resolve
|
|
385
|
-
this.fieldsToResolve.push(dbKey);
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
if (field.valid) {
|
|
389
|
-
// TODO this could be async. handle this better
|
|
390
|
-
let valid = await Promise.resolve(field.valid(value));
|
|
391
|
-
if (!valid) {
|
|
392
|
-
throw new Error(`invalid field ${field.name} with value ${value}`);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
if (field.format) {
|
|
396
|
-
// TODO this could be async e.g. password. handle this better
|
|
397
|
-
value = await Promise.resolve(field.format(value));
|
|
398
|
-
}
|
|
399
|
-
}
|
|
424
|
+
value = await this.transformFieldValue(field, dbKey, value);
|
|
400
425
|
if (value !== undefined) {
|
|
401
426
|
data[dbKey] = value;
|
|
402
427
|
logValues[dbKey] = field.logValue(value);
|
|
403
428
|
}
|
|
404
429
|
}
|
|
430
|
+
// we ignored default values while editing.
|
|
431
|
+
// if we're editing and there's data, add default values
|
|
432
|
+
if (op === action_1.WriteOperation.Edit && this.hasData(data)) {
|
|
433
|
+
for (const fieldName in this.defaultFieldsByFieldName) {
|
|
434
|
+
const defaultValue = this.defaultFieldsByFieldName[fieldName];
|
|
435
|
+
let field = schemaFields.get(fieldName);
|
|
436
|
+
let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(field.name);
|
|
437
|
+
// no value, let's just default
|
|
438
|
+
if (data[dbKey] === undefined) {
|
|
439
|
+
const value = await this.transformFieldValue(field, dbKey, defaultValue);
|
|
440
|
+
data[dbKey] = value;
|
|
441
|
+
logValues[dbKey] = field.logValue(value);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
405
445
|
this.validatedFields = data;
|
|
406
446
|
this.logValues = logValues;
|
|
407
447
|
}
|
|
@@ -423,7 +463,6 @@ class Orchestrator {
|
|
|
423
463
|
await this.validX();
|
|
424
464
|
let ops = [this.buildMainOp()];
|
|
425
465
|
await this.buildEdgeOps(ops);
|
|
426
|
-
// console.log("post build");
|
|
427
466
|
return new EntChangeset(this.options.viewer, this.options.builder.placeholderID, this.options.loaderOptions.ent, ops, this.dependencies, this.changesets, this.options);
|
|
428
467
|
}
|
|
429
468
|
async viewerForEntLoad(data) {
|
package/core/ent.d.ts
CHANGED
|
@@ -61,6 +61,7 @@ export declare class EditNodeOperation<T extends Ent> implements DataOperation {
|
|
|
61
61
|
placeholderID?: ID | undefined;
|
|
62
62
|
constructor(options: EditNodeOptions<T>, existingEnt?: Ent | null);
|
|
63
63
|
resolve<T extends Ent>(executor: Executor): void;
|
|
64
|
+
private hasData;
|
|
64
65
|
performWrite(queryer: Queryer, context?: Context): Promise<void>;
|
|
65
66
|
private reloadRow;
|
|
66
67
|
performWriteSync(queryer: SyncQueryer, context?: Context): void;
|
package/core/ent.js
CHANGED
|
@@ -421,13 +421,24 @@ class EditNodeOperation {
|
|
|
421
421
|
});
|
|
422
422
|
this.options.fields = fields;
|
|
423
423
|
}
|
|
424
|
+
hasData(data) {
|
|
425
|
+
for (const _k in data) {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
424
430
|
async performWrite(queryer, context) {
|
|
425
431
|
let options = {
|
|
426
432
|
...this.options,
|
|
427
433
|
context,
|
|
428
434
|
};
|
|
429
435
|
if (this.existingEnt) {
|
|
430
|
-
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
|
+
}
|
|
431
442
|
}
|
|
432
443
|
else {
|
|
433
444
|
this.row = await createRow(queryer, options, "RETURNING *");
|
|
@@ -455,8 +466,13 @@ class EditNodeOperation {
|
|
|
455
466
|
context,
|
|
456
467
|
};
|
|
457
468
|
if (this.existingEnt) {
|
|
458
|
-
|
|
459
|
-
|
|
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
|
+
}
|
|
460
476
|
}
|
|
461
477
|
else {
|
|
462
478
|
createRowSync(queryer, options, "RETURNING *");
|
package/graphql/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export { GraphQLEdgeType, GraphQLConnectionType, } from "./query/connection_type
|
|
|
6
6
|
export { GraphQLNodeInterface } from "./builtins/node";
|
|
7
7
|
export { GraphQLConnectionInterface } from "./builtins/connection";
|
|
8
8
|
export { GraphQLEdgeInterface } from "./builtins/edge";
|
|
9
|
-
export { NodeResolver, EntNodeResolver, registerResolver, clearResolvers, resolveID, nodeIDEncoder, mustDecodeIDFromGQLID, encodeGQLID, } from "./node_resolver";
|
|
9
|
+
export { NodeResolver, EntNodeResolver, registerResolver, clearResolvers, resolveID, nodeIDEncoder, mustDecodeIDFromGQLID, mustDecodeNullableIDFromGQLID, encodeGQLID, } from "./node_resolver";
|
|
10
10
|
export { convertFromGQLEnum, convertToGQLEnum } from "./enums";
|
package/graphql/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.convertToGQLEnum = exports.convertFromGQLEnum = exports.encodeGQLID = exports.mustDecodeIDFromGQLID = exports.nodeIDEncoder = exports.resolveID = exports.clearResolvers = exports.registerResolver = exports.EntNodeResolver = exports.GraphQLEdgeInterface = exports.GraphQLConnectionInterface = exports.GraphQLNodeInterface = exports.GraphQLConnectionType = exports.GraphQLEdgeType = exports.GraphQLEdgeConnection = exports.GraphQLPageInfo = exports.GraphQLTime = exports.gqlFileUpload = exports.GQLCapture = exports.gqlConnection = exports.gqlContextType = exports.gqlMutation = exports.gqlQuery = exports.gqlObjectType = exports.gqlInputObjectType = exports.gqlArgType = exports.gqlArg = exports.gqlField = void 0;
|
|
3
|
+
exports.convertToGQLEnum = exports.convertFromGQLEnum = exports.encodeGQLID = exports.mustDecodeNullableIDFromGQLID = exports.mustDecodeIDFromGQLID = exports.nodeIDEncoder = exports.resolveID = exports.clearResolvers = exports.registerResolver = exports.EntNodeResolver = exports.GraphQLEdgeInterface = exports.GraphQLConnectionInterface = exports.GraphQLNodeInterface = exports.GraphQLConnectionType = exports.GraphQLEdgeType = exports.GraphQLEdgeConnection = exports.GraphQLPageInfo = exports.GraphQLTime = exports.gqlFileUpload = exports.GQLCapture = exports.gqlConnection = exports.gqlContextType = exports.gqlMutation = exports.gqlQuery = exports.gqlObjectType = exports.gqlInputObjectType = exports.gqlArgType = exports.gqlArg = exports.gqlField = void 0;
|
|
4
4
|
var graphql_1 = require("./graphql");
|
|
5
5
|
Object.defineProperty(exports, "gqlField", { enumerable: true, get: function () { return graphql_1.gqlField; } });
|
|
6
6
|
Object.defineProperty(exports, "gqlArg", { enumerable: true, get: function () { return graphql_1.gqlArg; } });
|
|
@@ -35,6 +35,7 @@ Object.defineProperty(exports, "clearResolvers", { enumerable: true, get: functi
|
|
|
35
35
|
Object.defineProperty(exports, "resolveID", { enumerable: true, get: function () { return node_resolver_1.resolveID; } });
|
|
36
36
|
Object.defineProperty(exports, "nodeIDEncoder", { enumerable: true, get: function () { return node_resolver_1.nodeIDEncoder; } });
|
|
37
37
|
Object.defineProperty(exports, "mustDecodeIDFromGQLID", { enumerable: true, get: function () { return node_resolver_1.mustDecodeIDFromGQLID; } });
|
|
38
|
+
Object.defineProperty(exports, "mustDecodeNullableIDFromGQLID", { enumerable: true, get: function () { return node_resolver_1.mustDecodeNullableIDFromGQLID; } });
|
|
38
39
|
Object.defineProperty(exports, "encodeGQLID", { enumerable: true, get: function () { return node_resolver_1.encodeGQLID; } });
|
|
39
40
|
var enums_1 = require("./enums");
|
|
40
41
|
Object.defineProperty(exports, "convertFromGQLEnum", { enumerable: true, get: function () { return enums_1.convertFromGQLEnum; } });
|
|
@@ -24,5 +24,6 @@ export declare function clearResolvers(): Promise<void>;
|
|
|
24
24
|
export declare function resolveID(viewer: Viewer, id: string): Promise<Node | null>;
|
|
25
25
|
export declare const nodeIDEncoder: GraphQLFieldResolver<Ent, {}>;
|
|
26
26
|
export declare function mustDecodeIDFromGQLID(id: string): ID;
|
|
27
|
+
export declare function mustDecodeNullableIDFromGQLID(id: string | null | undefined): any;
|
|
27
28
|
export declare function encodeGQLID(node: Ent): string;
|
|
28
29
|
export {};
|
package/graphql/node_resolver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.encodeGQLID = exports.mustDecodeIDFromGQLID = exports.nodeIDEncoder = exports.resolveID = exports.clearResolvers = exports.registerResolver = exports.EntNodeResolver = void 0;
|
|
3
|
+
exports.encodeGQLID = exports.mustDecodeNullableIDFromGQLID = exports.mustDecodeIDFromGQLID = exports.nodeIDEncoder = exports.resolveID = exports.clearResolvers = exports.registerResolver = exports.EntNodeResolver = void 0;
|
|
4
4
|
class EntNodeResolver {
|
|
5
5
|
constructor(loader) {
|
|
6
6
|
this.loader = loader;
|
|
@@ -76,6 +76,19 @@ function mustDecodeIDFromGQLID(id) {
|
|
|
76
76
|
return decoded;
|
|
77
77
|
}
|
|
78
78
|
exports.mustDecodeIDFromGQLID = mustDecodeIDFromGQLID;
|
|
79
|
+
// TODO get the right (non-any) return type here. may need to change codegen to do the right thing here
|
|
80
|
+
function mustDecodeNullableIDFromGQLID(id) {
|
|
81
|
+
// support undefined because fields in action
|
|
82
|
+
if (id === null || id === undefined) {
|
|
83
|
+
return id;
|
|
84
|
+
}
|
|
85
|
+
const decoded = EntNodeResolver.decode(id);
|
|
86
|
+
if (!decoded) {
|
|
87
|
+
throw new Error(`wasn't able to decode invalid ${id}`);
|
|
88
|
+
}
|
|
89
|
+
return decoded;
|
|
90
|
+
}
|
|
91
|
+
exports.mustDecodeNullableIDFromGQLID = mustDecodeNullableIDFromGQLID;
|
|
79
92
|
// This takes an ent and returns the graphql id
|
|
80
93
|
function encodeGQLID(node) {
|
|
81
94
|
// let's do 3 parts. we take the "node" prefix
|
package/package.json
CHANGED
package/parse_schema/parse.js
CHANGED
|
@@ -16,6 +16,14 @@ function processFields(processedSchema, src) {
|
|
|
16
16
|
f["polymorphic"] = field.polymorphic;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
// convert string to object to make API consumed by go simple
|
|
20
|
+
if (f.fieldEdge && f.fieldEdge.inverseEdge) {
|
|
21
|
+
if (typeof f.fieldEdge.inverseEdge === "string") {
|
|
22
|
+
f.fieldEdge.inverseEdge = {
|
|
23
|
+
name: f.fieldEdge.inverseEdge,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
19
27
|
processedSchema.fields.push(f);
|
|
20
28
|
}
|
|
21
29
|
}
|
package/schema/base_schema.js
CHANGED
package/schema/field.d.ts
CHANGED
|
@@ -17,8 +17,11 @@ export declare abstract class BaseField {
|
|
|
17
17
|
logValue(val: any): any;
|
|
18
18
|
}
|
|
19
19
|
export declare class UUIDField extends BaseField implements Field {
|
|
20
|
+
private options;
|
|
20
21
|
type: Type;
|
|
21
22
|
constructor(options: FieldOptions);
|
|
23
|
+
private isBuilder;
|
|
24
|
+
valid(val: any): Promise<boolean>;
|
|
22
25
|
}
|
|
23
26
|
export declare function UUIDType(options: FieldOptions): UUIDField;
|
|
24
27
|
export interface IntegerOptions extends FieldOptions {
|
|
@@ -141,7 +144,7 @@ export declare class ListField extends BaseField {
|
|
|
141
144
|
private validators;
|
|
142
145
|
constructor(field: Field, options: FieldOptions);
|
|
143
146
|
validate(validator: (val: any[]) => boolean): this;
|
|
144
|
-
valid(val: any): boolean
|
|
147
|
+
valid(val: any): Promise<boolean>;
|
|
145
148
|
private postgresVal;
|
|
146
149
|
format(val: any): any;
|
|
147
150
|
minLen(l: number): this;
|
package/schema/field.js
CHANGED
|
@@ -22,8 +22,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
exports.UUIDListType = exports.EnumListType = exports.DateListType = exports.TimetzListType = exports.TimeListType = exports.TimestamptzListType = exports.TimestampListType = exports.BooleanListType = exports.BigIntegerListType = exports.FloatListType = exports.IntegerListType = exports.IntListType = exports.StringListType = exports.ListField = exports.EnumType = exports.EnumField = exports.DateType = exports.DateField = exports.TimetzType = exports.TimeType = exports.TimeField = exports.leftPad = exports.TimestamptzType = exports.TimestampType = exports.TimestampField = exports.StringType = exports.StringField = exports.BooleanType = exports.BooleanField = exports.FloatType = exports.FloatField = exports.BigIntegerType = exports.BigIntegerField = exports.IntegerType = exports.IntegerField = exports.UUIDType = exports.UUIDField = exports.BaseField = void 0;
|
|
23
23
|
const luxon_1 = require("luxon");
|
|
24
24
|
const snake_case_1 = require("snake-case");
|
|
25
|
+
const camel_case_1 = require("camel-case");
|
|
25
26
|
const db_1 = __importStar(require("../core/db"));
|
|
26
27
|
const schema_1 = require("./schema");
|
|
28
|
+
const util_1 = require("util");
|
|
27
29
|
class BaseField {
|
|
28
30
|
logValue(val) {
|
|
29
31
|
if (this.sensitive) {
|
|
@@ -37,6 +39,7 @@ exports.BaseField = BaseField;
|
|
|
37
39
|
class UUIDField extends BaseField {
|
|
38
40
|
constructor(options) {
|
|
39
41
|
super();
|
|
42
|
+
this.options = options;
|
|
40
43
|
this.type = { dbType: schema_1.DBType.UUID };
|
|
41
44
|
const polymorphic = options.polymorphic;
|
|
42
45
|
if (polymorphic) {
|
|
@@ -80,6 +83,32 @@ class UUIDField extends BaseField {
|
|
|
80
83
|
];
|
|
81
84
|
}
|
|
82
85
|
}
|
|
86
|
+
if (options.fieldEdge?.enforceSchema &&
|
|
87
|
+
!options.fieldEdge.getLoaderOptions) {
|
|
88
|
+
throw new Error(`cannot enforceSchema if getLoaderOptions wasn't passed in`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
isBuilder(val) {
|
|
92
|
+
return val.placeholderID !== undefined;
|
|
93
|
+
}
|
|
94
|
+
async valid(val) {
|
|
95
|
+
if (!this.options.fieldEdge?.enforceSchema) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
// TODO #510 consistency.
|
|
99
|
+
const f = (0, camel_case_1.camelCase)(this.options.fieldEdge.schema);
|
|
100
|
+
const getLoaderOptions = this.options.fieldEdge.getLoaderOptions;
|
|
101
|
+
const loadRowOptions = getLoaderOptions(f);
|
|
102
|
+
if (!loadRowOptions) {
|
|
103
|
+
throw new Error(`couldn't get loaderOptions for ${f}`);
|
|
104
|
+
}
|
|
105
|
+
if (this.isBuilder(val)) {
|
|
106
|
+
// if builder, the ent type of the builder and the ent type returned by the load constructor should match
|
|
107
|
+
return val.ent === loadRowOptions.ent;
|
|
108
|
+
}
|
|
109
|
+
// TODO we need context here to make sure that we hit local cache
|
|
110
|
+
const row = await loadRowOptions.loaderFactory.createLoader().load(val);
|
|
111
|
+
return row !== null;
|
|
83
112
|
}
|
|
84
113
|
}
|
|
85
114
|
exports.UUIDField = UUIDField;
|
|
@@ -542,7 +571,7 @@ class ListField extends BaseField {
|
|
|
542
571
|
this.validators.push(validator);
|
|
543
572
|
return this;
|
|
544
573
|
}
|
|
545
|
-
valid(val) {
|
|
574
|
+
async valid(val) {
|
|
546
575
|
if (!Array.isArray(val)) {
|
|
547
576
|
return false;
|
|
548
577
|
}
|
|
@@ -551,15 +580,18 @@ class ListField extends BaseField {
|
|
|
551
580
|
return false;
|
|
552
581
|
}
|
|
553
582
|
}
|
|
554
|
-
|
|
583
|
+
const valid = this.field.valid;
|
|
584
|
+
if (!valid) {
|
|
555
585
|
return true;
|
|
556
586
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
587
|
+
const res = valid.apply(this.field, [val[0]]);
|
|
588
|
+
if (util_1.types.isPromise(res)) {
|
|
589
|
+
const ret = await Promise.all(val.map(async (v) => await valid.apply(this.field, [v])));
|
|
590
|
+
return ret.every((v) => v);
|
|
561
591
|
}
|
|
562
|
-
|
|
592
|
+
const ret = val.map((v) => valid.apply(this.field, [v]));
|
|
593
|
+
const result = ret.every((v) => v);
|
|
594
|
+
return result;
|
|
563
595
|
}
|
|
564
596
|
postgresVal(val, jsonType) {
|
|
565
597
|
if (!jsonType) {
|
package/schema/schema.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Data, Ent } from "../core/base";
|
|
1
|
+
import { Data, Ent, LoadEntOptions } from "../core/base";
|
|
2
2
|
import { Builder } from "../action/action";
|
|
3
3
|
export default interface Schema {
|
|
4
4
|
fields: Field[];
|
|
@@ -100,10 +100,21 @@ export interface ForeignKey {
|
|
|
100
100
|
column: string;
|
|
101
101
|
name?: string;
|
|
102
102
|
disableIndex?: boolean;
|
|
103
|
+
disableBuilderType?: boolean;
|
|
104
|
+
}
|
|
105
|
+
declare type getLoaderOptionsFn = (type: any) => LoadEntOptions<Ent>;
|
|
106
|
+
export interface InverseFieldEdge {
|
|
107
|
+
name: string;
|
|
108
|
+
edgeConstName?: string;
|
|
109
|
+
tableName?: string;
|
|
110
|
+
hideFromGraphQL?: boolean;
|
|
103
111
|
}
|
|
104
112
|
export interface FieldEdge {
|
|
105
113
|
schema: string;
|
|
106
|
-
inverseEdge
|
|
114
|
+
inverseEdge?: string | InverseFieldEdge;
|
|
115
|
+
enforceSchema?: boolean;
|
|
116
|
+
getLoaderOptions?: getLoaderOptionsFn;
|
|
117
|
+
disableBuilderType?: boolean;
|
|
107
118
|
}
|
|
108
119
|
export interface FieldOptions {
|
|
109
120
|
name: string;
|
|
@@ -130,10 +141,11 @@ export interface FieldOptions {
|
|
|
130
141
|
export interface PolymorphicOptions {
|
|
131
142
|
types?: string[];
|
|
132
143
|
hideFromInverseGraphQL?: boolean;
|
|
144
|
+
disableBuilderType?: boolean;
|
|
133
145
|
}
|
|
134
146
|
export interface Field extends FieldOptions {
|
|
135
147
|
type: Type;
|
|
136
|
-
valid?(val: any): boolean;
|
|
148
|
+
valid?(val: any): Promise<boolean> | boolean;
|
|
137
149
|
format?(val: any): any;
|
|
138
150
|
logValue(val: any): any;
|
|
139
151
|
}
|
|
@@ -159,6 +171,7 @@ export interface ActionField {
|
|
|
159
171
|
nullable?: boolean | NullableListOptions;
|
|
160
172
|
list?: boolean;
|
|
161
173
|
actionName?: string;
|
|
174
|
+
excludedFields?: string[];
|
|
162
175
|
}
|
|
163
176
|
export interface Action {
|
|
164
177
|
operation: ActionOperation;
|
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>;
|
|
@@ -100,5 +100,6 @@ export declare class SimpleAction<T extends Ent> implements Action<T> {
|
|
|
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
|
@@ -203,16 +203,19 @@ class SimpleAction {
|
|
|
203
203
|
async save() {
|
|
204
204
|
await (0, action_1.saveBuilder)(this.builder);
|
|
205
205
|
if (this.builder.operation !== action_1.WriteOperation.Delete) {
|
|
206
|
-
return
|
|
206
|
+
return this.builder.orchestrator.editedEnt();
|
|
207
207
|
}
|
|
208
208
|
return null;
|
|
209
209
|
}
|
|
210
210
|
async saveX() {
|
|
211
211
|
await (0, action_1.saveBuilderX)(this.builder);
|
|
212
|
-
return
|
|
212
|
+
return this.builder.orchestrator.editedEntX();
|
|
213
213
|
}
|
|
214
214
|
async editedEnt() {
|
|
215
|
-
return
|
|
215
|
+
return this.builder.orchestrator.editedEnt();
|
|
216
|
+
}
|
|
217
|
+
async editedEntX() {
|
|
218
|
+
return this.builder.orchestrator.editedEntX();
|
|
216
219
|
}
|
|
217
220
|
}
|
|
218
221
|
exports.SimpleAction = SimpleAction;
|
|
@@ -27,6 +27,7 @@ export declare function expectQueryFromRoot(config: queryRootConfig, ...options:
|
|
|
27
27
|
export interface mutationRootConfig extends queryConfig {
|
|
28
28
|
mutation: string;
|
|
29
29
|
disableInputWrapping?: boolean;
|
|
30
|
+
nullQueryPaths?: string[];
|
|
30
31
|
}
|
|
31
32
|
export declare function expectMutation(config: mutationRootConfig, ...options: Option[]): Promise<supertest.SuperTest<supertest.Test>>;
|
|
32
33
|
export {};
|
package/testutils/parse_sql.js
CHANGED
|
@@ -121,6 +121,22 @@ function getDataToReturn(data, colNames, returningAll) {
|
|
|
121
121
|
return ret;
|
|
122
122
|
}
|
|
123
123
|
exports.getDataToReturn = getDataToReturn;
|
|
124
|
+
function processBeforeStoring(val) {
|
|
125
|
+
if (typeof val !== "string") {
|
|
126
|
+
return val;
|
|
127
|
+
}
|
|
128
|
+
// convert postgres lists into lists before storing
|
|
129
|
+
if (val[0] === "{" && val[val.length - 1] === "}") {
|
|
130
|
+
try {
|
|
131
|
+
// valid json, don't convert
|
|
132
|
+
JSON.parse(val);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
return val.substring(1, val.length - 1).split(",");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return val;
|
|
139
|
+
}
|
|
124
140
|
function parseInsertStatement(ast, values, // values passed to query
|
|
125
141
|
returningAll) {
|
|
126
142
|
const tableName = getTableName(ast.table);
|
|
@@ -138,7 +154,7 @@ returningAll) {
|
|
|
138
154
|
// INSERT INTO tableName (cols) VALUES (pos args)
|
|
139
155
|
for (let i = 0; i < columns.length; i++) {
|
|
140
156
|
let col = columns[i];
|
|
141
|
-
data[col] = values[i];
|
|
157
|
+
data[col] = processBeforeStoring(values[i]);
|
|
142
158
|
}
|
|
143
159
|
let returningData = null;
|
|
144
160
|
if (returningAll) {
|
|
@@ -411,7 +427,7 @@ function parseUpdateStatement(ast, values, map, returningAll) {
|
|
|
411
427
|
for (const set of ast.set) {
|
|
412
428
|
let col = set.column;
|
|
413
429
|
let value = getValueFromRegex(set.value, values);
|
|
414
|
-
overwrite[col] = value;
|
|
430
|
+
overwrite[col] = processBeforeStoring(value);
|
|
415
431
|
}
|
|
416
432
|
let columns = new Set();
|
|
417
433
|
if (ast.returning) {
|