@snowtop/ent 0.1.0-alpha145 → 0.1.0-alpha146

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.
@@ -8,7 +8,7 @@ import * as clause from "../core/clause";
8
8
  type MaybeNull<T extends Ent> = T | null;
9
9
  type TMaybleNullableEnt<T extends Ent> = T | MaybeNull<T>;
10
10
  export interface OrchestratorOptions<TEnt extends Ent<TViewer>, TInput extends Data, TViewer extends Viewer, TExistingEnt extends TMaybleNullableEnt<TEnt> = MaybeNull<TEnt>> {
11
- viewer: Viewer;
11
+ viewer: TViewer;
12
12
  operation: WriteOperation;
13
13
  tableName: string;
14
14
  loaderOptions: LoadEntOptions<TEnt, TViewer>;
@@ -68,6 +68,7 @@ export declare class Orchestrator<TEnt extends Ent<TViewer>, TInput extends Data
68
68
  private getEdgeOperation;
69
69
  private buildEdgeOps;
70
70
  private throwError;
71
+ private getRowForPrivacyPolicyImpl;
71
72
  private getEntForPrivacyPolicyImpl;
72
73
  private getSQLStatementOperation;
73
74
  private getWriteOpForSQLStamentOp;
@@ -51,31 +51,38 @@ class edgeInputData {
51
51
  return id.placeholderID !== undefined;
52
52
  }
53
53
  }
54
- function getViewer(action) {
55
- if (!action.viewer.viewerID) {
54
+ function getViewer(viewer) {
55
+ if (!viewer.viewerID) {
56
56
  return "Logged out Viewer";
57
57
  }
58
58
  else {
59
- return `Viewer with ID ${action.viewer.viewerID}`;
59
+ return `Viewer with ID ${viewer.viewerID}`;
60
60
  }
61
61
  }
62
62
  class EntCannotCreateEntError extends Error {
63
63
  constructor(privacyPolicy, action) {
64
- let msg = `${getViewer(action)} does not have permission to create ${action.builder.ent.name}`;
64
+ let msg = `${getViewer(action.viewer)} does not have permission to create ${action.builder.ent.name}`;
65
65
  super(msg);
66
66
  this.privacyPolicy = privacyPolicy;
67
67
  }
68
68
  }
69
69
  class EntCannotEditEntError extends Error {
70
70
  constructor(privacyPolicy, action, ent) {
71
- let msg = `${getViewer(action)} does not have permission to edit ${ent.constructor.name}`;
71
+ let msg = `${getViewer(action.viewer)} does not have permission to edit ${ent.constructor.name}`;
72
+ super(msg);
73
+ this.privacyPolicy = privacyPolicy;
74
+ }
75
+ }
76
+ class EntCannotEditEntFieldError extends Error {
77
+ constructor(privacyPolicy, viewer, field, ent) {
78
+ let msg = `${getViewer(viewer)} does not have permission to edit field ${field} ${ent.constructor.name}`;
72
79
  super(msg);
73
80
  this.privacyPolicy = privacyPolicy;
74
81
  }
75
82
  }
76
83
  class EntCannotDeleteEntError extends Error {
77
84
  constructor(privacyPolicy, action, ent) {
78
- let msg = `${getViewer(action)} does not have permission to delete ${ent.constructor.name}`;
85
+ let msg = `${getViewer(action.viewer)} does not have permission to delete ${ent.constructor.name}`;
79
86
  super(msg);
80
87
  this.privacyPolicy = privacyPolicy;
81
88
  }
@@ -314,10 +321,7 @@ class Orchestrator {
314
321
  }
315
322
  return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
316
323
  }
317
- async getEntForPrivacyPolicyImpl(schemaFields, editedData) {
318
- if (this.actualOperation !== action_1.WriteOperation.Insert) {
319
- return this.existingEnt;
320
- }
324
+ async getRowForPrivacyPolicyImpl(schemaFields, editedData) {
321
325
  // need to format fields if possible because ent constructors expect data that's
322
326
  // in the format that's coming from the db
323
327
  // required for object fields...
@@ -350,8 +354,17 @@ class Orchestrator {
350
354
  }
351
355
  formatted[dbKey] = val;
352
356
  }
357
+ return formatted;
358
+ }
359
+ async getEntForPrivacyPolicyImpl(schemaFields, editedData, viewerToUse, rowToUse) {
360
+ if (this.actualOperation !== action_1.WriteOperation.Insert) {
361
+ return this.existingEnt;
362
+ }
363
+ if (!rowToUse) {
364
+ rowToUse = await this.getRowForPrivacyPolicyImpl(schemaFields, editedData);
365
+ }
353
366
  // we create an unsafe ent to be used for privacy policies
354
- return new this.options.builder.ent(this.options.builder.viewer, formatted);
367
+ return new this.options.builder.ent(viewerToUse, rowToUse);
355
368
  }
356
369
  getSQLStatementOperation() {
357
370
  switch (this.actualOperation) {
@@ -382,7 +395,7 @@ class Orchestrator {
382
395
  return this.existingEnt;
383
396
  }
384
397
  const { schemaFields, editedData } = await this.memoizedGetFields();
385
- return this.getEntForPrivacyPolicyImpl(schemaFields, editedData);
398
+ return this.getEntForPrivacyPolicyImpl(schemaFields, editedData, this.options.viewer);
386
399
  }
387
400
  // this gets the fields that were explicitly set plus any default or transformed values
388
401
  // mainly exists to get default fields e.g. default id to be used in triggers
@@ -408,9 +421,17 @@ class Orchestrator {
408
421
  const builder = this.options.builder;
409
422
  // future optimization: can get schemaFields to memoize based on different values
410
423
  const schemaFields = (0, schema_1.getFields)(this.options.schema);
424
+ // also future optimization, no need to go through the list of fields multiple times
425
+ const editPrivacyFields = (0, schema_1.getFieldsWithEditPrivacy)(this.options.schema, this.options.fieldInfo);
411
426
  const editedFields = await this.options.editedFields();
412
- let editedData = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
413
- return { editedData, editedFields, schemaFields };
427
+ let { data: editedData, userDefinedKeys } = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
428
+ return {
429
+ editedData,
430
+ editedFields,
431
+ schemaFields,
432
+ userDefinedKeys,
433
+ editPrivacyFields,
434
+ };
414
435
  }
415
436
  async validate() {
416
437
  // existing ent required for edit or delete operations
@@ -421,7 +442,7 @@ class Orchestrator {
421
442
  throw new Error(`existing ent required with operation ${this.actualOperation}`);
422
443
  }
423
444
  }
424
- const { schemaFields, editedData } = await this.memoizedGetFields();
445
+ const { schemaFields, editedData, userDefinedKeys, editPrivacyFields } = await this.memoizedGetFields();
425
446
  const action = this.options.action;
426
447
  const builder = this.options.builder;
427
448
  // this runs in following phases:
@@ -430,18 +451,40 @@ class Orchestrator {
430
451
  // * triggers
431
452
  // * validators
432
453
  let privacyPolicy = action?.getPrivacyPolicy();
433
- let privacyError = null;
454
+ const errors = [];
434
455
  if (privacyPolicy) {
456
+ const ent = await this.getEntForPrivacyPolicyImpl(schemaFields, editedData, this.options.viewer);
435
457
  try {
436
- await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, await this.getEntForPrivacyPolicyImpl(schemaFields, editedData), this.throwError.bind(this));
458
+ await (0, privacy_1.applyPrivacyPolicyX)(this.options.viewer, privacyPolicy, ent, () => this.throwError());
437
459
  }
438
460
  catch (err) {
439
- privacyError = err;
461
+ errors.push(err);
440
462
  }
441
463
  }
442
- // privacyError should return first since it's less confusing
443
- if (privacyError !== null) {
444
- return [privacyError];
464
+ // we have edit privacy fields, so we need to apply privacy policy on those
465
+ const promises = [];
466
+ if (editPrivacyFields.size) {
467
+ // get row based on edited data
468
+ const row = await this.getRowForPrivacyPolicyImpl(schemaFields, editedData);
469
+ // get viewer for ent load based on formatted row
470
+ const viewer = await this.viewerForEntLoad(row);
471
+ const ent = await this.getEntForPrivacyPolicyImpl(schemaFields, editedData, viewer, row);
472
+ for (const [k, policy] of editPrivacyFields) {
473
+ if (editedData[k] === undefined || !userDefinedKeys.has(k)) {
474
+ continue;
475
+ }
476
+ promises.push((async () => {
477
+ const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
478
+ if (!r) {
479
+ errors.push(new EntCannotEditEntFieldError(policy, viewer, k, ent));
480
+ }
481
+ })());
482
+ }
483
+ await Promise.all(promises);
484
+ }
485
+ // privacy or field errors should return first so it's less confusing
486
+ if (errors.length) {
487
+ return errors;
445
488
  }
446
489
  // have to run triggers which update fields first before field and other validators
447
490
  // so running this first to build things up
@@ -455,11 +498,12 @@ class Orchestrator {
455
498
  // not ideal we're calling this twice. fix...
456
499
  // needed for now. may need to rewrite some of this?
457
500
  const editedFields2 = await this.options.editedFields();
458
- const [errors, errs2] = await Promise.all([
501
+ const [errs2, errs3] = await Promise.all([
459
502
  this.formatAndValidateFields(schemaFields, editedFields2),
460
503
  this.validators(validators, action, builder),
461
504
  ]);
462
505
  errors.push(...errs2);
506
+ errors.push(...errs3);
463
507
  return errors;
464
508
  }
465
509
  async triggers(action, builder, triggers) {
@@ -595,11 +639,15 @@ class Orchestrator {
595
639
  }
596
640
  // transforming before doing default fields so that we don't create a new id
597
641
  // and anything that depends on the type of operations knows what it is
642
+ const userDefinedKeys = new Set();
598
643
  for (const [fieldName, field] of schemaFields) {
599
644
  let value = editedFields.get(fieldName);
600
645
  let defaultValue = undefined;
601
646
  let dbKey = this.getStorageKey(fieldName);
602
647
  let updateOnlyIfOther = field.onlyUpdateIfOtherFieldsBeingSet_BETA;
648
+ if (value !== undefined) {
649
+ userDefinedKeys.add(dbKey);
650
+ }
603
651
  if (value === undefined) {
604
652
  if (this.actualOperation === action_1.WriteOperation.Insert) {
605
653
  if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
@@ -652,7 +700,7 @@ class Orchestrator {
652
700
  this.options.updateInput(this.defaultFieldsByTSName);
653
701
  }
654
702
  }
655
- return data;
703
+ return { data, userDefinedKeys };
656
704
  }
657
705
  hasData(data) {
658
706
  for (const _k in data) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowtop/ent",
3
- "version": "0.1.0-alpha145",
3
+ "version": "0.1.0-alpha146",
4
4
  "description": "snowtop ent framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -53,6 +53,7 @@ type ProcessedField = Omit<Field, "defaultValueOnEdit" | "defaultValueOnCreate"
53
53
  hasDefaultValueOnEdit?: boolean;
54
54
  patternName?: string;
55
55
  hasFieldPrivacy?: boolean;
56
+ hasEditFieldPrivacy?: boolean;
56
57
  derivedFields?: ProcessedField[];
57
58
  type: ProcessedType;
58
59
  serverDefault?: string;
@@ -26,6 +26,7 @@ async function processFields(src, patternName) {
26
26
  f.hasDefaultValueOnCreate = field.defaultValueOnCreate != undefined;
27
27
  f.hasDefaultValueOnEdit = field.defaultValueOnEdit != undefined;
28
28
  f.hasFieldPrivacy = field.privacyPolicy !== undefined;
29
+ f.hasEditFieldPrivacy = field.editPrivacyPolicy !== undefined;
29
30
  if (field.polymorphic) {
30
31
  // convert boolean into object
31
32
  // we keep boolean as an option to keep API simple
package/schema/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import Schema from "./schema";
2
2
  export { Schema };
3
- export { Field, AssocEdge, AssocEdgeGroup, InverseAssocEdge, Edge, Pattern, DBType, Type, FieldOptions, SchemaConstructor, SchemaInputType, getFields, getFieldsWithPrivacy, getStorageKey, ActionOperation, Action, EdgeAction, NoFields, FieldMap, Constraint, Index, ConstraintType, ForeignKeyInfo, requiredField, optionalField, UpdateOperation, TransformedUpdateOperation, SQLStatementOperation, EdgeUpdateOperation, TransformedEdgeUpdateOperation, getTransformedReadClause, getObjectLoaderProperties, GlobalSchema, ActionField, } from "./schema";
3
+ export { Field, AssocEdge, AssocEdgeGroup, InverseAssocEdge, Edge, Pattern, DBType, Type, FieldOptions, SchemaConstructor, SchemaInputType, getFields, getFieldsWithPrivacy, getFieldsWithEditPrivacy, getStorageKey, ActionOperation, Action, EdgeAction, NoFields, FieldMap, Constraint, Index, ConstraintType, ForeignKeyInfo, requiredField, optionalField, UpdateOperation, TransformedUpdateOperation, SQLStatementOperation, EdgeUpdateOperation, TransformedEdgeUpdateOperation, getTransformedReadClause, getObjectLoaderProperties, GlobalSchema, ActionField, } from "./schema";
4
4
  export { Timestamps, Node, BaseEntSchema, BaseEntSchemaWithTZ, EntSchema, EntSchemaWithTZ, SchemaConfig, } from "./base_schema";
5
5
  export * from "./field";
6
6
  export * from "./json_field";
package/schema/index.js CHANGED
@@ -14,11 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.EntSchemaWithTZ = exports.EntSchema = exports.BaseEntSchemaWithTZ = exports.BaseEntSchema = exports.Node = exports.Timestamps = exports.getObjectLoaderProperties = exports.getTransformedReadClause = exports.SQLStatementOperation = exports.optionalField = exports.requiredField = exports.ConstraintType = exports.NoFields = exports.ActionOperation = exports.getStorageKey = exports.getFieldsWithPrivacy = exports.getFields = exports.DBType = void 0;
17
+ exports.EntSchemaWithTZ = exports.EntSchema = exports.BaseEntSchemaWithTZ = exports.BaseEntSchema = exports.Node = exports.Timestamps = exports.getObjectLoaderProperties = exports.getTransformedReadClause = exports.SQLStatementOperation = exports.optionalField = exports.requiredField = exports.ConstraintType = exports.NoFields = exports.ActionOperation = exports.getStorageKey = exports.getFieldsWithEditPrivacy = exports.getFieldsWithPrivacy = exports.getFields = exports.DBType = void 0;
18
18
  var schema_1 = require("./schema");
19
19
  Object.defineProperty(exports, "DBType", { enumerable: true, get: function () { return schema_1.DBType; } });
20
20
  Object.defineProperty(exports, "getFields", { enumerable: true, get: function () { return schema_1.getFields; } });
21
21
  Object.defineProperty(exports, "getFieldsWithPrivacy", { enumerable: true, get: function () { return schema_1.getFieldsWithPrivacy; } });
22
+ Object.defineProperty(exports, "getFieldsWithEditPrivacy", { enumerable: true, get: function () { return schema_1.getFieldsWithEditPrivacy; } });
22
23
  Object.defineProperty(exports, "getStorageKey", { enumerable: true, get: function () { return schema_1.getStorageKey; } });
23
24
  Object.defineProperty(exports, "ActionOperation", { enumerable: true, get: function () { return schema_1.ActionOperation; } });
24
25
  Object.defineProperty(exports, "NoFields", { enumerable: true, get: function () { return schema_1.NoFields; } });
@@ -234,6 +234,7 @@ export interface FieldOptions {
234
234
  derivedWhenEmbedded?: boolean;
235
235
  polymorphic?: boolean | PolymorphicOptions;
236
236
  privacyPolicy?: PrivacyPolicy | (() => PrivacyPolicy);
237
+ editPrivacyPolicy?: PrivacyPolicy | (() => PrivacyPolicy);
237
238
  getDerivedFields?(name: string): FieldMap;
238
239
  convert?: ConvertType;
239
240
  fetchOnDemand?: boolean;
@@ -265,7 +266,8 @@ export declare function getFields(value: SchemaInputType): Map<string, Field>;
265
266
  * @deprecated should only be used by tests
266
267
  */
267
268
  export declare function getStorageKey(field: Field, fieldName: string): string;
268
- export declare function getFieldsWithPrivacy(value: SchemaInputType, fieldMap: FieldInfoMap): Map<string, PrivacyPolicy>;
269
+ export declare function getFieldsWithPrivacy(value: SchemaInputType, fieldInfoMap: FieldInfoMap): Map<string, PrivacyPolicy>;
270
+ export declare function getFieldsWithEditPrivacy(value: SchemaInputType, fieldInfoMap: FieldInfoMap): Map<string, PrivacyPolicy>;
269
271
  export declare function getTransformedReadClause(value: SchemaInputType): Clause | undefined;
270
272
  interface objectLoaderOptions {
271
273
  clause?: () => Clause | undefined;
package/schema/schema.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ConstraintType = exports.optionalField = exports.requiredField = exports.NoFields = exports.ActionOperation = exports.getTransformedUpdateOp = exports.getObjectLoaderProperties = exports.getTransformedReadClause = exports.getFieldsWithPrivacy = exports.getStorageKey = exports.getFields = exports.getSchema = exports.DBType = exports.SQLStatementOperation = void 0;
3
+ exports.ConstraintType = exports.optionalField = exports.requiredField = exports.NoFields = exports.ActionOperation = exports.getTransformedUpdateOp = exports.getObjectLoaderProperties = exports.getTransformedReadClause = exports.getFieldsWithEditPrivacy = exports.getFieldsWithPrivacy = exports.getStorageKey = exports.getFields = exports.getSchema = exports.DBType = exports.SQLStatementOperation = void 0;
4
4
  const snake_case_1 = require("snake-case");
5
5
  // we also want this transformation to exist on a per-action basis
6
6
  // if it exists on an action, we don't do the global schema transformation
@@ -85,49 +85,31 @@ function getStorageKey(field, fieldName) {
85
85
  }
86
86
  exports.getStorageKey = getStorageKey;
87
87
  // returns a mapping of storage key to field privacy
88
- function getFieldsWithPrivacy(value, fieldMap) {
88
+ function getFieldsWithPrivacy(value, fieldInfoMap) {
89
+ return getFieldsWithPrivacyImpl(value, fieldInfoMap, "privacyPolicy");
90
+ }
91
+ exports.getFieldsWithPrivacy = getFieldsWithPrivacy;
92
+ function getFieldsWithEditPrivacy(value, fieldInfoMap) {
93
+ return getFieldsWithPrivacyImpl(value, fieldInfoMap, "editPrivacyPolicy");
94
+ }
95
+ exports.getFieldsWithEditPrivacy = getFieldsWithEditPrivacy;
96
+ function getFieldsWithPrivacyImpl(value, fieldInfoMap, key) {
89
97
  const schema = getSchema(value);
90
98
  function addFields(fields) {
91
- if (Array.isArray(fields)) {
92
- for (const field of fields) {
93
- const name = field.name;
94
- if (!field.name) {
95
- throw new Error(`name required`);
96
- }
97
- if (field.getDerivedFields !== undefined) {
98
- addFields(field.getDerivedFields(name));
99
- }
100
- if (field.privacyPolicy) {
101
- let privacyPolicy;
102
- if (typeof field.privacyPolicy === "function") {
103
- privacyPolicy = field.privacyPolicy();
104
- }
105
- else {
106
- privacyPolicy = field.privacyPolicy;
107
- }
108
- const info = fieldMap[name];
109
- if (!info) {
110
- throw new Error(`field with name ${name} not passed in fieldMap`);
111
- }
112
- m.set(info.dbCol, privacyPolicy);
113
- }
114
- }
115
- return;
116
- }
117
99
  for (const name in fields) {
118
100
  const field = fields[name];
101
+ if (field.dbOnly) {
102
+ continue;
103
+ }
119
104
  if (field.getDerivedFields !== undefined) {
120
105
  addFields(field.getDerivedFields(name));
121
106
  }
122
- if (field.privacyPolicy) {
123
- let privacyPolicy;
124
- if (typeof field.privacyPolicy === "function") {
125
- privacyPolicy = field.privacyPolicy();
126
- }
127
- else {
128
- privacyPolicy = field.privacyPolicy;
107
+ let privacyPolicy = field[key];
108
+ if (privacyPolicy) {
109
+ if (typeof privacyPolicy === "function") {
110
+ privacyPolicy = privacyPolicy();
129
111
  }
130
- const info = fieldMap[name];
112
+ const info = fieldInfoMap[name];
131
113
  if (!info) {
132
114
  throw new Error(`field with name ${name} not passed in fieldMap`);
133
115
  }
@@ -144,7 +126,6 @@ function getFieldsWithPrivacy(value, fieldMap) {
144
126
  addFields(schema.fields);
145
127
  return m;
146
128
  }
147
- exports.getFieldsWithPrivacy = getFieldsWithPrivacy;
148
129
  function getTransformedReadClause(value) {
149
130
  const schema = getSchema(value);
150
131
  if (!schema.patterns) {