@snowtop/ent 0.0.29 → 0.0.32-alpha

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,84 @@ 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
+ if (field.valid) {
384
+ const valid = await Promise.resolve(field.valid(value));
385
+ if (!valid) {
386
+ throw new Error(`invalid field ${field.name} with value ${value}`);
387
+ }
388
+ }
389
+ // keep track of dependencies to resolve
390
+ this.dependencies.set(value.placeholderID, value);
391
+ // keep track of fields to resolve
392
+ this.fieldsToResolve.push(dbKey);
393
+ }
394
+ else {
395
+ if (field.valid) {
396
+ // TODO this could be async. handle this better
397
+ const valid = await Promise.resolve(field.valid(value));
398
+ if (!valid) {
399
+ throw new Error(`invalid field ${field.name} with value ${value}`);
400
+ }
401
+ }
402
+ if (field.format) {
403
+ // TODO this could be async e.g. password. handle this better
404
+ value = await Promise.resolve(field.format(value));
405
+ }
406
+ }
407
+ return value;
408
+ }
348
409
  async formatAndValidateFields(schemaFields) {
349
- if (this.options.operation == action_1.WriteOperation.Delete) {
410
+ const op = this.options.operation;
411
+ if (op === action_1.WriteOperation.Delete) {
350
412
  return;
351
413
  }
352
414
  const editedFields = this.options.editedFields();
@@ -355,53 +417,32 @@ class Orchestrator {
355
417
  let logValues = {};
356
418
  for (const [fieldName, field] of schemaFields) {
357
419
  let value = editedFields.get(fieldName);
358
- if (value === undefined) {
420
+ if (value === undefined && op === action_1.WriteOperation.Insert) {
359
421
  // null allowed
360
422
  value = this.defaultFieldsByFieldName[fieldName];
361
423
  }
362
424
  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
- }
425
+ value = await this.transformFieldValue(field, dbKey, value);
400
426
  if (value !== undefined) {
401
427
  data[dbKey] = value;
402
428
  logValues[dbKey] = field.logValue(value);
403
429
  }
404
430
  }
431
+ // we ignored default values while editing.
432
+ // if we're editing and there's data, add default values
433
+ if (op === action_1.WriteOperation.Edit && this.hasData(data)) {
434
+ for (const fieldName in this.defaultFieldsByFieldName) {
435
+ const defaultValue = this.defaultFieldsByFieldName[fieldName];
436
+ let field = schemaFields.get(fieldName);
437
+ let dbKey = field.storageKey || (0, snake_case_1.snakeCase)(field.name);
438
+ // no value, let's just default
439
+ if (data[dbKey] === undefined) {
440
+ const value = await this.transformFieldValue(field, dbKey, defaultValue);
441
+ data[dbKey] = value;
442
+ logValues[dbKey] = field.logValue(value);
443
+ }
444
+ }
445
+ }
405
446
  this.validatedFields = data;
406
447
  this.logValues = logValues;
407
448
  }
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.29",
3
+ "version": "0.0.32-alpha",
4
4
  "description": "snowtop ent framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -37,7 +37,7 @@ let nodeField = (0, field_1.UUIDType)({
37
37
  return (0, uuid_1.v4)();
38
38
  },
39
39
  });
40
- let nodeFields = [nodeField].concat(tsFields);
40
+ let nodeFields = [nodeField, ...tsFields];
41
41
  let nodeFieldsWithTZ = [
42
42
  nodeField,
43
43
  (0, field_1.TimestampType)({
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 {
package/schema/field.js CHANGED
@@ -37,6 +37,7 @@ exports.BaseField = BaseField;
37
37
  class UUIDField extends BaseField {
38
38
  constructor(options) {
39
39
  super();
40
+ this.options = options;
40
41
  this.type = { dbType: schema_1.DBType.UUID };
41
42
  const polymorphic = options.polymorphic;
42
43
  if (polymorphic) {
@@ -80,6 +81,26 @@ class UUIDField extends BaseField {
80
81
  ];
81
82
  }
82
83
  }
84
+ if (options.fieldEdge?.enforceSchema && !options.fieldEdge.loadRowByType) {
85
+ throw new Error(`cannot enforceSchema if loadRowByType wasn't passed in`);
86
+ }
87
+ }
88
+ isBuilder(val) {
89
+ return val.placeholderID !== undefined;
90
+ }
91
+ async valid(val) {
92
+ if (!this.options.fieldEdge?.enforceSchema) {
93
+ return true;
94
+ }
95
+ const loadRowByType = this.options.fieldEdge.loadRowByType;
96
+ const loadRowOptions = loadRowByType(this.options.fieldEdge.schema);
97
+ if (this.isBuilder(val)) {
98
+ // if builder, the ent type of the builder and the ent type returned by the load constructor should match
99
+ return val.ent === loadRowOptions.ent;
100
+ }
101
+ // TODO we need context here to make sure that we hit local cache
102
+ const row = await loadRowOptions.loaderFactory.createLoader().load(val);
103
+ return row !== null;
83
104
  }
84
105
  }
85
106
  exports.UUIDField = UUIDField;
@@ -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[];
@@ -101,9 +101,12 @@ export interface ForeignKey {
101
101
  name?: string;
102
102
  disableIndex?: boolean;
103
103
  }
104
+ declare type loadRowFn = (type: string) => LoadEntOptions<Ent>;
104
105
  export interface FieldEdge {
105
106
  schema: string;
106
- inverseEdge: string;
107
+ inverseEdge?: string;
108
+ enforceSchema?: boolean;
109
+ loadRowByType?: loadRowFn;
107
110
  }
108
111
  export interface FieldOptions {
109
112
  name: string;
@@ -133,7 +136,7 @@ export interface PolymorphicOptions {
133
136
  }
134
137
  export interface Field extends FieldOptions {
135
138
  type: Type;
136
- valid?(val: any): boolean;
139
+ valid?(val: any): Promise<boolean> | boolean;
137
140
  format?(val: any): any;
138
141
  logValue(val: any): any;
139
142
  }
@@ -168,6 +171,10 @@ export interface Action {
168
171
  graphQLName?: string;
169
172
  hideFromGraphQL?: boolean;
170
173
  actionOnlyFields?: ActionField[];
174
+ excludedFields?: string[];
175
+ optionalFields?: string[];
176
+ requiredFields?: string[];
177
+ noFields?: boolean;
171
178
  }
172
179
  export declare const NoFields = "__NO_FIELDS__";
173
180
  export declare function requiredField(field: string): string;
@@ -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;