@snowtop/ent 0.0.30 → 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 +1 -1
- package/action/experimental_action.d.ts +3 -1
- package/action/experimental_action.js +26 -1
- package/action/orchestrator.d.ts +2 -0
- package/action/orchestrator.js +80 -44
- package/core/ent.d.ts +1 -0
- package/core/ent.js +19 -3
- package/package.json +1 -1
- package/testutils/builder.d.ts +3 -2
- package/testutils/builder.js +6 -3
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,6 +59,8 @@ 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>;
|
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) {
|
|
@@ -330,23 +331,79 @@ class Orchestrator {
|
|
|
330
331
|
// TODO special case this if this is the only thing changing and don't do the write.
|
|
331
332
|
}
|
|
332
333
|
}
|
|
333
|
-
|
|
334
|
+
if (value !== undefined) {
|
|
335
|
+
data[dbKey] = value;
|
|
336
|
+
}
|
|
334
337
|
if (defaultValue !== undefined) {
|
|
335
338
|
updateInput = true;
|
|
336
|
-
|
|
339
|
+
defaultData[dbKey] = defaultValue;
|
|
337
340
|
this.defaultFieldsByFieldName[fieldName] = defaultValue;
|
|
338
341
|
// TODO related to #510. we need this logic to be consistent so do this all in TypeScript or get it from go somehow
|
|
339
342
|
this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(fieldName)] = defaultValue;
|
|
340
343
|
}
|
|
341
344
|
}
|
|
342
|
-
if
|
|
343
|
-
|
|
344
|
-
|
|
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
|
+
}
|
|
345
355
|
}
|
|
346
356
|
return data;
|
|
347
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
|
+
}
|
|
348
404
|
async formatAndValidateFields(schemaFields) {
|
|
349
|
-
|
|
405
|
+
const op = this.options.operation;
|
|
406
|
+
if (op === action_1.WriteOperation.Delete) {
|
|
350
407
|
return;
|
|
351
408
|
}
|
|
352
409
|
const editedFields = this.options.editedFields();
|
|
@@ -355,53 +412,32 @@ class Orchestrator {
|
|
|
355
412
|
let logValues = {};
|
|
356
413
|
for (const [fieldName, field] of schemaFields) {
|
|
357
414
|
let value = editedFields.get(fieldName);
|
|
358
|
-
if (value === undefined) {
|
|
415
|
+
if (value === undefined && op === action_1.WriteOperation.Insert) {
|
|
359
416
|
// null allowed
|
|
360
417
|
value = this.defaultFieldsByFieldName[fieldName];
|
|
361
418
|
}
|
|
362
419
|
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
|
-
}
|
|
420
|
+
value = await this.transformFieldValue(field, dbKey, value);
|
|
400
421
|
if (value !== undefined) {
|
|
401
422
|
data[dbKey] = value;
|
|
402
423
|
logValues[dbKey] = field.logValue(value);
|
|
403
424
|
}
|
|
404
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
|
+
}
|
|
405
441
|
this.validatedFields = data;
|
|
406
442
|
this.logValues = logValues;
|
|
407
443
|
}
|
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/package.json
CHANGED
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;
|