@snowtop/ent 0.0.30 → 0.0.32-alpha.3

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) {
@@ -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
- data[dbKey] = value;
333
+ if (value !== undefined) {
334
+ data[dbKey] = value;
335
+ }
334
336
  if (defaultValue !== undefined) {
335
337
  updateInput = true;
336
- data[dbKey] = defaultValue;
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 (updateInput && this.options.updateInput) {
343
- // this basically fixes #605. just needs to be exposed correctly
344
- this.options.updateInput(this.defaultFieldsByTSName);
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
- if (this.options.operation == action_1.WriteOperation.Delete) {
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
- // 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
- }
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.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.32-alpha.3",
4
4
  "description": "snowtop ent framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -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
  }
@@ -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[];
@@ -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 loadRowFn = (type: string) => 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: string;
114
+ inverseEdge?: string | InverseFieldEdge;
115
+ enforceSchema?: boolean;
116
+ loadRowByType?: loadRowFn;
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
  }
@@ -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;