@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.
@@ -7,7 +7,7 @@ export declare enum WriteOperation {
7
7
  Delete = "delete"
8
8
  }
9
9
  export interface Builder<T extends Ent> {
10
- existingEnt?: Ent;
10
+ existingEnt?: T;
11
11
  ent: EntConstructor<T>;
12
12
  placeholderID: ID;
13
13
  readonly viewer: Viewer;
@@ -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;
@@ -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>;
@@ -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
- data[dbKey] = value;
334
+ if (value !== undefined) {
335
+ data[dbKey] = value;
336
+ }
334
337
  if (defaultValue !== undefined) {
335
338
  updateInput = true;
336
- data[dbKey] = defaultValue;
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 (updateInput && this.options.updateInput) {
343
- // this basically fixes #605. just needs to be exposed correctly
344
- this.options.updateInput(this.defaultFieldsByTSName);
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
- if (this.options.operation == action_1.WriteOperation.Delete) {
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
- // now format and validate...
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.row = await editRow(queryer, options, this.existingEnt.id, "RETURNING *");
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
- editRowSync(queryer, options, this.existingEnt.id, "RETURNING *");
459
- this.reloadRow(queryer, this.existingEnt.id, options);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowtop/ent",
3
- "version": "0.0.30",
3
+ "version": "0.0.31",
4
4
  "description": "snowtop ent framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -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: Ent | undefined;
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?: Ent | undefined, action?: Action<T> | undefined);
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 {};
@@ -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 await this.builder.orchestrator.editedEnt();
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 await this.builder.orchestrator.editedEntX();
212
+ return this.builder.orchestrator.editedEntX();
213
213
  }
214
214
  async editedEnt() {
215
- return await this.builder.orchestrator.editedEnt();
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;